Skip to content

Initial support for razor isolation files (cs, css, js)#12981

Merged
ToddGrun merged 13 commits intodotnet:mainfrom
ToddGrun:dev/toddgrun/RazorIsolationFiles
Apr 2, 2026
Merged

Initial support for razor isolation files (cs, css, js)#12981
ToddGrun merged 13 commits intodotnet:mainfrom
ToddGrun:dev/toddgrun/RazorIsolationFiles

Conversation

@ToddGrun
Copy link
Copy Markdown
Contributor

This adds contextmenu support to solution explorer in VS for creating and viewing razor isolation files in support of this feedback ticket: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1903761

Menu items are hooked up through the RazorPackage, thus requiring an appropriate UIContextRule to handle when appropriate files are right clicked on. This also required registering the razor package as allowing to be loaded on a background thread, something we should have been allowing anyway.

The actual implementation to create the files was kept very simple. I originally had coded up the c# implementation to walk up the hierarchy and parse editor configs, but it added more complexity than it's worth without feedback that it's wanted (verified this decision with Sayed)

Most of the project interaction is done through DTE as this is implemented at the VS extension layer. Would love to hear feedback about whether another choice would be more appropriate. Also, there wasn't an existing unit test project for MS.VS.RazorExtensions, so I wasn't sure what to do about unit tests (and it made me doubt my location of choice).

This adds contextmenu support to solution explorer in VS for creating and viewing razor isolation files in support of this feedback ticket: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1903761

Menu items are hooked up through the RazorPackage, thus requiring an appropriate UIContextRule to handle when appropriate files are right clicked on. This also required registering the razor package as allowing to be loaded on a background thread, something we should have been allowing anyway.

The actual implementation to create the files was kept very simple. I originally had coded up the c# implementation to walk up the hierarchy and parse editor configs, but it added more complexity than it's worth without feedback that it's wanted (verified this decision with Sayed)

Most of the project interaction is done through DTE as this is implemented at the VS extension layer. Would love to hear feedback about whether another choice would be more appropriate.
@ToddGrun ToddGrun requested a review from a team as a code owner March 31, 2026 18:34
Copy link
Copy Markdown
Member

@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.

Other than the minor bike-shedding comments I've left, the menu items and the "View" command side of things here is all good.

Unfortunately I think the "Add" part needs to change, and importantly, needs to go call into the LSP server to do the work.

Reasons:

  • The namespace calculation here is probably both overcomplicated, but also not complicated enough. Namespaces are supplied to the compiler by logic in the SDK, and users can have @namespace directives in Razor files, and/or import files, and the closest one wins, and a user could have unsaved changes in an directive that they expect to apply when using the command. Fortunately we get all of the "for free" if we're in the LSP server, as we can call TryGetNamespace and it will cover all of those bases, plus any other complications we decided to add in future.
    • We also already have code there that creates a code behind file, and even makes sure its formatted nicely by Roslyn, which could be shared by this function. In future when we add a @className directive, it would be good if there was only one place to update.
  • I think for .cshtml.css, .razor.js and .cshtml.js, the files aren't loaded automatically and so I think we need to edit the Razor file to include a <script>/<style> reference for it. We probably can't do a great job of finding a good spot to put the tag, but we could just slap it at the end and hope the user knows what they're doing. Not sure if this part was specced, or needs to be. /cc @sayedihashimi
  • If we put it in the LSP server, we can easily test it with our existing infra (call the endpoint, get the workspace edit, check it/apply it to an in-memory project). Existing cohost tests do this already so we should be pretty well set up for it.
  • If we put it in the LSP server, we can create VS Code commands to do these functions if we want. /cc @sayedihashimi

Making an LSP request from the VS layer is easy enough with the request invoker (example), and the handler in the LSP server can get the LSP client to make the files and edits we need via workspace/applyEdit (example). This also has the advantage of removing a lot of the DTE code here for adding files to the project etc. and letting the LSP client worry about doing it (and it will make an undo transaction, check files out from source control, etc. etc.).

Happy to provide more in-depth details if I've not been clear about any of this.

<value>Add C# Code-Behind File</value>
</data>
<data name="View_Code_Behind_File" xml:space="preserve">
<value>View C# Code-Behind File</value>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Interestingly, I seem to already have a menu item that does this, called "Go to PageModel". Clearly named wrong, I guess leaking out of Web Tools.
image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@sayedihashimi -- Can you help reconcile how this fits in? Looks like the GotoPageModel is on the solution explorer for cshtml, GotoPage is on the solution explorer for cs, and there are context menu items in the editor for these too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I didn't realize that we had those commands. I wonder if it would be simpler to leave the commands as Add Weather.cshtml.css, if the file already exists just open it in the editor. What do you think of that? The alternative is to hide Add Weather.cshtml.css when the file exists but that suffers from:

  • Users get confused when commands appear/disapper
  • Time it takes to figure that out, context menu commands must be very fast

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm going to see about removing the "Go to PageModel" entry and ensure that these changes give equivalent functionality.

namespace Microsoft.VisualStudio.RazorExtension;

[PackageRegistration(UseManagedResourcesOnly = true)]
[PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would RPS notice this? Might be worth a val build so we can at least give infra people a heads up (can you tell i'm infra next sprint? 😛)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It probably won't be noticed, although it's a good thing for us. We should probably try to clean up our package initialization to be more background thread permissive, as the first thing it does is SwitchToMainThreadAsync. But, I will get a perf run on this change once it's gotten a bit closer to it's finished state.

@davidwengier
Copy link
Copy Markdown
Member

davidwengier commented Mar 31, 2026

I originally had coded up the c# implementation to walk up the hierarchy and parse editor configs

I'm curious, because I can't think of anything, but which part of the editorconfig file would affect this?

EDIT: Oh, a thought! If it's about file headers, file scoped namespaces etc. for the new .cs file, the GetFormattedNewFileContentsAsync method we call in the second link in my above comment handles all of that :)

@ToddGrun
Copy link
Copy Markdown
Contributor Author

  • I think for .cshtml.css, .razor.js and .cshtml.js, the files aren't loaded automatically and so I think we need to edit the Razor file to include a <script>/<style> reference for it. We probably can't do a great job of finding a good spot to put the tag, but we could just slap it at the end and hope the user knows what they're doing. Not sure if this part was specced, or needs to be.

@sayedihashimi -- Can you verify that we should be updating the razor/cshtml file with links to the added js/css file?

Add a Razor LSP endpoint and OOP remote service that generates and creates isolation files (CSS, C#, JS) for Razor components via workspace/applyEdit.
Replace DTE-based file generation in the VS command handlers with thin LSP client calls to the new isolation file endpoint. Save the file after creation to avoid a dirty buffer in the editor.
Add tests covering CSS, C#, and JS isolation file generation including content validation and namespace resolution.
@sayedihashimi
Copy link
Copy Markdown
Member

sayedihashimi commented Apr 1, 2026

It looks like CSS Isolation is working for CSHTML files in both Razor Pages and MVC projects. I tested with this sample https://github.com/sayedihashimi/sample-projects/tree/2026.04.01.css01.

For JS files it doesn't seem to work. Since it's not working, I don't think it's a good idea for us to add js files to the context menu because those should be in the wwwroot folder.

cc @danroth27

image

@sayedihashimi
Copy link
Copy Markdown
Member

Other than the minor bike-shedding comments I've left, the menu items and the "View" command side of things here is all good.

Unfortunately I think the "Add" part needs to change, and importantly, needs to go call into the LSP server to do the work.

Reasons:

  • The namespace calculation here is probably both overcomplicated, but also not complicated enough. Namespaces are supplied to the compiler by logic in the SDK, and users can have @namespace directives in Razor files, and/or import files, and the closest one wins, and a user could have unsaved changes in an directive that they expect to apply when using the command. Fortunately we get all of the "for free" if we're in the LSP server, as we can call TryGetNamespace and it will cover all of those bases, plus any other complications we decided to add in future.

    • We also already have code there that creates a code behind file, and even makes sure its formatted nicely by Roslyn, which could be shared by this function. In future when we add a @className directive, it would be good if there was only one place to update.
  • I think for .cshtml.css, .razor.js and .cshtml.js, the files aren't loaded automatically and so I think we need to edit the Razor file to include a <script>/<style> reference for it. We probably can't do a great job of finding a good spot to put the tag, but we could just slap it at the end and hope the user knows what they're doing. Not sure if this part was specced, or needs to be. /cc @sayedihashimi

  • If we put it in the LSP server, we can easily test it with our existing infra (call the endpoint, get the workspace edit, check it/apply it to an in-memory project). Existing cohost tests do this already so we should be pretty well set up for it.

  • If we put it in the LSP server, we can create VS Code commands to do these functions if we want. /cc @sayedihashimi

Making an LSP request from the VS layer is easy enough with the request invoker (example), and the handler in the LSP server can get the LSP client to make the files and edits we need via workspace/applyEdit (example). This also has the advantage of removing a lot of the DTE code here for adding files to the project etc. and letting the LSP client worry about doing it (and it will make an undo transaction, check files out from source control, etc. etc.).

Happy to provide more in-depth details if I've not been clear about any of this.

RE "If we put it in the LSP server, we can create VS Code commands to do these functions if we want. /cc @sayedihashimi"

I like having that ability down the line, but I'm not sure if we would surface this in VS Code. I think we can scope this to VS.

@sayedihashimi
Copy link
Copy Markdown
Member

@sayedihashimi -- Can you verify that we should be updating the razor/cshtml file with links to the added js/css file?

I would prefer that we only support what is built-in to ASP.NET Core so that we don't need to modify the users code to add tags, it's hard for us to figure out the correct place to put that tag.

@ToddGrun
Copy link
Copy Markdown
Contributor Author

ToddGrun commented Apr 1, 2026

The namespace calculation here is probably both overcomplicated, but also not complicated enough. Namespaces are supplied to the compiler by logic in the SDK, and users can have @namespace directives in Razor files, and/or import files, and the closest one wins, and a user could have unsaved changes in an directive that they expect to apply when using the command. Fortunately we get all of the "for free" if we're in the LSP server, as we can call TryGetNamespace and it will cover all of those bases, plus any other complications we decided to add in future.
We also already have code there that creates a code behind file, and even makes sure its formatted nicely by Roslyn, which could be shared by this function. In future when we add a @classname directive, it would be good if there was only one place to update.

@davidwengier -- This comment was great, and very helpful. AI helped me make this change, so let me know if things better align now with what you were thinking.

Add unit tests to validate add C# file uses editor config
@danroth27
Copy link
Copy Markdown
Member

For JS files it doesn't seem to work. Since it's not working, I don't think it's a good idea for us to add js files to the context menu because those should be in the wwwroot folder.

You can have a .cshtml.js file in an MVC or Razor Pages app alongside a corresponding .cshtml view or page and it will be treated as a static web asset just like .razor.js files are.

Copy link
Copy Markdown
Member

@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 is looking great, and probably works just fine, but I think it would be good to get it a little more idiomatic before merging.

Comment on lines +54 to +65
if (!solution.TryGetRazorDocument(razorFileUri, out var razorDocument))
{
Logger.LogWarning($"Could not find Razor document for URI: {razorFileUri}");
return null;
}

var documentContext = CreateRazorDocumentContext(solution, razorDocument.Id);
if (documentContext is null)
{
Logger.LogWarning($"Could not create document context for: {razorFileUri}");
return null;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I haven't looked at where this is called from yet, but if Uri razorFileUri can be a JsonSerializableDocumentId, then all of this is done for you, and will be generally more reliable.

private static string GenerateCssContent(string razorFilePath)
{
var componentName = Path.GetFileNameWithoutExtension(razorFilePath);
var fileType = FileKinds.GetFileKindFromPath(razorFilePath).IsComponent() ? "component" : "view";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should fix this at some point, but I always prefer this check to be inverted. ie:

Suggested change
var fileType = FileKinds.GetFileKindFromPath(razorFilePath).IsComponent() ? "component" : "view";
var fileType = FileKinds.GetFileKindFromPath(razorFilePath).IsLegacy() ? "view" : "component";

The reason is that _Imports.razor returns false for IsComponent() so it would end up being described as a view. Having said that, we probably shouldn't bother showing these menu items for _Imports.razor or ViewImports.cshtml anyway, so if we do that restriction, this comment becomes moot. 🤷‍♂️

Comment on lines +175 to +191
var usingDirectives = razorCodeDocument
.GetRequiredDocumentNode()
.FindDescendantNodes<UsingDirectiveIntermediateNode>();

foreach (var usingDirective in usingDirectives)
{
builder.Append("using ");

var content = usingDirective.Content;
var startIndex = content.StartsWith("global::", StringComparison.Ordinal)
? 8
: 0;

builder.Append(content, startIndex, content.Length - startIndex);
builder.Append(';');
builder.AppendLine();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The FormatNewFile function also removes unused usings, which for this file would be all of these, so there is no point doing all of this :)

private readonly IRemoteServiceInvoker _remoteServiceInvoker = remoteServiceInvoker;
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<CohostAddNestedFileEndpoint>();

protected override bool MutatesSolutionState => true;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should be false, it doesn't mutate anything, it asks the LSP client to do it. When the LSP client does, it will send didOpen/didChange requests, and those are marked as mutating. Having this true here will just slow throughput for the LSP server.

internal sealed class CohostAddNestedFileEndpoint(
IRemoteServiceInvoker remoteServiceInvoker,
ILoggerFactory loggerFactory)
: AbstractRazorCohostRequestHandler<AddNestedFileParams, VoidResult>
Copy link
Copy Markdown
Member

@davidwengier davidwengier Apr 1, 2026

Choose a reason for hiding this comment

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

This should be an AbstractCohostDocumentEndpoint, and AddNestedFileParams should implement ITextDocumentParams (ie, rename RazorFileUri to TextDocument), that way the LSP server will be able to better manage requests, solution snapshots etc. It will also mean context.TextDocument will be non-null, so you can grab the DocumentId to pass through to OOP etc.

If you need it, CohostWrapWithTagEndpoint is an example of a "custom" endpoint that does all of this.

/// Local request type matching <c>AddNestedFileParams</c> on the server side.
/// JSON property names must match the server's expected format.
/// </summary>
private sealed class AddNestedFileRequest
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm surprised this class can't just reference the one in LanguageServices. Or was this a deliberate choice?

Comment on lines +26 to +37
Assert.NotNull(result);
var changes = GetDocumentChanges(result);
Assert.Equal(2, changes.Length);

// First change: CreateFile
Assert.True(changes[0].TryGetSecond(out var createFile));
Assert.Contains(".razor.css", createFile!.DocumentUri.ToString());

// Second change: TextDocumentEdit with CSS comment content
Assert.True(changes[1].TryGetFirst(out var textEdit));
Assert.Single(textEdit!.Edits);
Assert.Contains("CSS for File1 component", ((TextEdit)textEdit.Edits[0]).NewText);
Copy link
Copy Markdown
Member

@davidwengier davidwengier Apr 2, 2026

Choose a reason for hiding this comment

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

I have absolutely no issue with the validation here, so feel free to leave this completely as is, but just FYI or in case you prefer:
We have an AssertWorkspaceEdit extension that could be used, so the tests could be written like:

VerifyCreateNestedFile(
    NestedFileKind.Css,
    additionalExpectedFiles: [(FileUri("File1.razor.css", """
        /* CSS file */
        """);

Where additionalExpectedFiles is (Uri fileUri, string contents)[]? additionalExpectedFiles, and the verify method calls await result.AssertWorkspaceEditAsync(document.Project.Solution, additionalExpectedFiles, DisposalToken);. See here for a similar verify method

/// Returns a <see cref="WorkspaceEdit"/> containing CreateFile + TextDocumentEdit operations,
/// or null if the operation could not be completed.
/// </summary>
ValueTask<WorkspaceEdit?> AddNestedFileAsync(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit, but a fairly large one: I think the method name here, and the doc comment, are misleading as this doesn't add/create a nested file. GetNewNestedFileEditAsync perhaps?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, that is definitely unclear from the current name

// Check if the Razor file context is active before doing expensive hierarchy queries
if (!IsRazorFileUIContextActive()
|| GetSelectedRazorFilePath() is not string razorFilePath)
{
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As mentioned in another comment, I think filtering out _Imports.razor or ViewImports.cshtml here would make sense, and fix the potential minor bug the other comment is about.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Doesn't GetSelectedRazorFilePath already do the filtering out of the import files via it's call to TryGetFileKindFromPath?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No, sadly that would only be true if Razor was a sensible codebase with good APIs. That call without validation of the returned RazorFileKind is just "does the filename end with .razor or .cshtml". Even if you add validation of RazorFileKind, and restricting to just IsComponent() or IsLegacy(), legacy imports files don't have their own file kind, so ViewImports.cshtml would still slip through.


command.Visible = true;
command.Enabled = true;
command.Text = string.Format(nestedFileExists ? Resources.View_Nested_File : Resources.Add_Nested_File, nestedFileName);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Super nit: There should be generated Resources.FormatView_Nested_File and Resources.Format_Add_Nested_File methods that can be called, and would ensure the number of parameters to format is correct. It also does culture stuff, which might be necessary for the right text to show up in other locales, but I've honestly no idea.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's a new one I hadn't seen before

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it's a Dustin-ism brought over from Winforms

ToddGrun and others added 3 commits April 1, 2026 21:37
Refactor CohostAddNestedFileEndpoint to extend AbstractCohostDocumentEndpoint
instead of AbstractRazorCohostRequestHandler, per PR review feedback. The base
class now handles document resolution automatically.

- Move AddNestedFileParams to Razor.Workspaces/Protocol/NestedFiles so both
  the cohost endpoint and RazorExtension command handler can share it. Add a
  Create() factory method (matching DocumentContentsRequest pattern) so callers
  don't need direct access to TextDocumentIdentifier.
- Implement ITextDocumentParams on AddNestedFileParams with TextDocumentIdentifier.
- Switch to [CohostEndpoint]/[ExportCohostStatelessLspService] attributes.
- Use Assumed.Unreachable for the abstract 3-param HandleRequestAsync override
  (same pattern as CohostInlineCompletionEndpoint).
- Remove unused code from RemoteAddNestedFileService.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ToddGrun
Copy link
Copy Markdown
Contributor Author

ToddGrun commented Apr 2, 2026

@davidwengier -- I think I've responded to all your comments

Copy link
Copy Markdown
Member

@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.

Lovely stuff

Logger.LogWarning($"Could not create document context for: {razorFileUri}");
return null;
}
System.Diagnostics.Debugger.Launch();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FYI, the "w/ ServiceHub" configs do this for you (though granted, this is more targeted)

image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually, nevermind, I bet those don't work at all any more after the move to devhub 🤦‍♂️

Assert.Contains("partial class File1", content);
await result.AssertWorkspaceEditAsync(
document.Project.Solution,
[(FileUri("File1.razor.cs"), """
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I wish I had realised this would happen, but validating the whole file makes it really clear the using directives weren't necessary. Love it!

if (!codeDocument.TryGetNamespace(fallbackToRootNamespace: true, out var ns))
{
Logger.LogWarning($"Could not determine namespace for: {razorFilePath}");
ns = "Unknown";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think if you pass inGlobalNamespace: true to CreateProjectAndRazorDocument in a test, we should get coverage of this. Might be worth it just for regression prevention. If it's not that simple, I wouldn't worry.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It was that easy, and it actually behaved differently than I was expecting. Good catch!

@ToddGrun ToddGrun merged commit 0ba060b into dotnet:main Apr 2, 2026
10 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Apr 2, 2026
chsienki added a commit that referenced this pull request Apr 2, 2026
Resolve merge conflict with PR #12990 (DocumentContext cleanup) and
PR #12981 (nested file support).
ToddGrun added a commit that referenced this pull request Apr 8, 2026
Finishes work for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1903761
Earlier PRs in support of this ticket:
  #12981
  https://devdiv.visualstudio.com/DevDiv/_git/WebTools/pullrequest/725194
  https://devdiv.visualstudio.com/DevDiv/_git/WebTools/pullrequest/725955

Add editor context menu commands for Razor nested files alongside the existing Solution Explorer commands. Uses VSCT CommandPlacements to share CSS/JS command IDs across both menus, and
IVsMonitorSelection.GetCurrentSelection to resolve the active file (which tracks the active window frame, not just Solution Explorer tree selection).

Key changes:

 - Unified NestedFileCommandHandler serves both Solution Explorer and editor menus via shared command IDs and SelectionHelper for selection/UIContext queries
 - Added editor context menu group (grpidRazorNestedFilesEditor) on IDM_VS_CTXT_CODEWIN with CSS/JS commands placed via CommandPlacements
 - For .cs in editor: intercept standard ViewCode (F7) command via UsedCommands so the F7 keybinding indicator displays automatically. Handler yields (Supported=false) to fall through to default ViewCode
when not in a Razor file
 - Added separate cmdidAddNestedCsFileEditor for the "Add .cs" case in editor, so it appears without the F7 keybinding when the .cs file doesn't exist
 - Added "View Page" command for navigating from nested files back to the parent Razor file, placed in editor, Solution Explorer, and CSS editor context menus
 - Extended UIContextRule to activate for nested files (.razor.* and .cshtml.*) in addition to .razor and .cshtml
 - ViewCodeCommandHandler (MEF F7 handler): returns Unavailable when .cs doesn't exist, excludes _Imports.razor and _ViewImports.cshtml, uses FileExistsHelper cache for per-keystroke performance
 - Added View_Page resource with translations, cleaned up unused Add_Code resource
 - Updated tests for new constructor signature and behavior
ToddGrun added a commit that referenced this pull request Apr 9, 2026
* Add editor context menu support for Razor nested file commands

Finishes work for https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1903761
Earlier PRs in support of this ticket:
  #12981
  https://devdiv.visualstudio.com/DevDiv/_git/WebTools/pullrequest/725194
  https://devdiv.visualstudio.com/DevDiv/_git/WebTools/pullrequest/725955

Add editor context menu commands for Razor nested files alongside the existing Solution Explorer commands. Uses VSCT CommandPlacements to share CSS/JS command IDs across both menus, and
IVsMonitorSelection.GetCurrentSelection to resolve the active file (which tracks the active window frame, not just Solution Explorer tree selection).

Key changes:

 - Unified NestedFileCommandHandler serves both Solution Explorer and editor menus via shared command IDs and SelectionHelper for selection/UIContext queries
 - Added editor context menu group (grpidRazorNestedFilesEditor) on IDM_VS_CTXT_CODEWIN with CSS/JS commands placed via CommandPlacements
 - For .cs in editor: intercept standard ViewCode (F7) command via UsedCommands so the F7 keybinding indicator displays automatically. Handler yields (Supported=false) to fall through to default ViewCode
when not in a Razor file
 - Added separate cmdidAddNestedCsFileEditor for the "Add .cs" case in editor, so it appears without the F7 keybinding when the .cs file doesn't exist
 - Added "View Page" command for navigating from nested files back to the parent Razor file, placed in editor, Solution Explorer, and CSS editor context menus
 - Extended UIContextRule to activate for nested files (.razor.* and .cshtml.*) in addition to .razor and .cshtml
 - ViewCodeCommandHandler (MEF F7 handler): returns Unavailable when .cs doesn't exist, excludes _Imports.razor and _ViewImports.cshtml, uses FileExistsHelper cache for per-keystroke performance
 - Added View_Page resource with translations, cleaned up unused Add_Code resource
 - Updated tests for new constructor signature and behavior

* unused using

* Remove optional params
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.

4 participants