Skip to content
| Marketplace
Sign in
Visual Studio Code>Visualization>Lean Solution Explorer for C#New to Visual Studio Code? Get it now.
Lean Solution Explorer for C#

Lean Solution Explorer for C#

taosif-bin-murtaza

|
9 installs
| (0) | Free
A fast, lazy-loading solution explorer for VS Code. Zero dependencies, no MSBuild.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Lean Solution Explorer for C#

Version Installs Downloads Rating License

Visual Studio's Solution Explorer for VS Code: the solution, its folders, its projects and their dependencies, read straight from the .sln or .slnx file. It is for people working in a C# solution in VS Code who want the tree, the NuGet window and the Run and Debug buttons they already know, without a language server or an MSBuild evaluation between them and the file list.

Click the Solution Explorer logo in the activity bar for four sections: Solutions, laid out the way Visual Studio's Solution Explorer lays it out - solution folders, projects, dependencies, files - then Bookmarks, TODO and Run Configurations.

What it does today

  • Every solution in the workspace loads. Multi-root workspaces and repositories with several .sln files all show up side by side. .slnx (the .NET 9+ XML solution format) is supported alongside classic .sln.
  • Solution structure, not just folders. Solution folders, their nesting, and SolutionItems files are rendered from the solution file itself. Solution folders sort above projects, and both the solution and each solution folder show how many projects sit below them.
  • Projects. SDK-style projects list what is actually on disk (minus bin, obj, .vs, .git, node_modules); legacy projects list their explicit <Compile Include="..."/> items folded into folders.
  • Dependencies. Target frameworks, project references and package references with their versions.
  • Edit. Every file and folder carries an Edit submenu: Cut, Copy, Paste, Delete and Rename.
  • Project references. Add and remove them from a project with a tick list that says which projects are already within reach through another reference, and which would close a loop.
  • Reference diagrams. Right-click a solution for two views of how its projects depend on each other, and two more of which NuGet packages they pull in and at which versions. All four carry a filter bar.
  • Endpoints Explorer. Every attribute-routed controller action and minimal API in a solution, opened as a form the way Swagger opens one: each value the action binds, with its C# type, and a body example shaped from the DTO it takes. Send the request from the tab.
  • Dead Code Explorer. The types nothing else in the solution mentions, grouped by category or by confidence, with the reason a finding was held back where there is one.
  • NuGet Package Manager. A tool window in the shape JetBrains Rider gives it: installed packages with the updates gathered at the top, a Discover search across your configured feeds, and every change run as a dotnet command in a terminal you can read.
  • NuGet Health. A solution-wide report beside it: which packages sit at different versions in different projects, which have known vulnerabilities, which are deprecated, and where a transitive dependency came from - with bulk fixes and a Central Package Management migration, each previewed before it writes.
  • Bookmarks. Mark a folder, a file, or the method under the cursor, and they come back with the workspace.
  • TODO. Every TODO, FIXME and HACK comment in the workspace, grouped by file.
  • Run Configurations. A startup project, the launch profiles it declares, and Run and Debug on the section's title bar.
  • Build, Clean, Rebuild and Publish. On the context menu of any solution or project, and in the Run Configurations section for the startup project. They run as VS Code tasks with the $msCompile problem matcher, so every MSBuild error lands in the Problems panel where you can click it. Publish asks what to publish - configuration, framework, runtime, deployment mode, output folder - and remembers the answers.
  • Tree line colour. The tree's indent guides can take a colour from the palette the extension's own icons are drawn in, or any hex colour you like. VS Code draws every tree in the window from one pair of colours, so this is mirrored into your workbench.colorCustomizations and the File Explorer, Search and Debug trees get the same lines.
  • Single click opens the file, in preview mode by default.
  • Auto reveal. The active editor's file is selected in the tree, expanding only the branch that leads to it.
  • Live updates. Adding or deleting a file redraws just the affected folder; editing a .csproj reloads just that project.
  • Add New Project. Right-click a solution or a solution folder and the new project is created there - the template list comes from dotnet new, and the project is filed in the solution folder you clicked.
  • Add New Item. Right-click a project or a folder inside one for a Class, Interface, Record, Struct or Enum, written into that folder with the namespace the folder implies. Add Directory and Add Scratch File sit below it for a plain folder or a file of any kind.

Requirements

VS Code 1.90 or later. That is the whole requirement for the tree: it has no runtime dependencies, needs no language server, and never starts a process to draw itself, so it works in a workspace with no .NET installed at all.

Two things are needed only by the features that reach outside the extension, and each one degrades on its own rather than taking anything else with it:

Needed For
The .NET SDK on PATH Add New Project...; the NuGet install, update and remove operations; the NuGet Health report; Build, Clean, Rebuild, Publish, Run and Debug
The C# extension, ms-dotnettools.csharp The coreclr debugger behind Debug, and the symbol used when bookmarking the method under the cursor
C# Dev Kit, ms-dotnettools.csdevkit (optional) The .NET: New Project... fallback Add New Project... offers when no SDK is on PATH

Without the SDK, the tree, the four diagrams, Bookmarks, TODO, the Endpoints Explorer, the Dead Code Explorer, Add New Item... and the NuGet Packages tab all still work. Add New Project... offers C# Dev Kit's .NET: New Project... instead, and NuGet Health falls back to the drift analysis, which is computable from the project files alone.

Without the C# extension, Run is unaffected - it is dotnet run and nothing else. Only Debug needs the debugger, and a bookmark with no symbol to name falls back to marking the line, which is never wrong.

The dotnet-backed actions also need a trusted workspace. In a restricted workspace the tree, the diagrams, the Endpoints and Dead Code explorers and the NuGet Packages tab all still open, and none of them starts a process. NuGet Health drops to the drift analysis it can compute from the project files; the three dotnet list package runs behind the vulnerability, deprecation and outdated columns are what needs the trust, and they are not made.

The extension walks the file system directly, so it needs a real one: a local folder, or a remote one over SSH, WSL or a dev container. It is disabled in virtual workspaces such as vscode.dev and github.dev, where there is no file system to walk.

Installation

From the Marketplace: search for C# Solution Explorer in the Extensions view, or open the listing and press Install.

From Quick Open (Ctrl+P, or Cmd+P on macOS):

ext install taosif-bin-murtaza.vscode-csharp-solution-explorer

From a locally built .vsix - npm run package writes one into the repository root:

code --install-extension vscode-csharp-solution-explorer-0.2.0.vsix

The same file can be installed from the Extensions view menu, under Install from VSIX....

Quick Start

  1. Open a folder holding a .sln, .slnx or a project file. Every solution in the workspace is found, including a multi-root one.
  2. Click the Solution Explorer logo in the activity bar. Solutions opens on what was found; expand one and it is parsed then, not before.
  3. Expand a project for its files, and Dependencies for its target frameworks, project references and packages.
  4. Right-click a project and choose Set as Startup Project.... With a single solution in the workspace one is chosen for you the first time it is needed.
  5. Press Run - the green triangle in the top right of the editor, and on the Run Configurations title bar - or Debug beside it. The configuration in force is on the Configuration row; click it to change it.

From there: right-click a solution for Manage NuGet Packages..., the reference and package diagrams, the Endpoints Explorer and the Dead Code Explorer.

Why it stays fast

The extension does no MSBuild evaluation and has no runtime dependencies. Reading the tree never starts a process; only the dotnet commands - Add New Project..., the NuGet operations, Build, Clean, Rebuild, Publish, Run and Debug - shell out, and only when you ask them to.

Decision Effect
Activates only in a workspace holding a solution or project file A repository with no .NET in it loads nothing
The view never waits on a workspace search Solutions found last time paint immediately; the scan runs behind them
Breadth-first scan that reports as it goes Solutions surface at the top of the tree first, not when the walk ends
Several solutions start collapsed Nothing is parsed until you click the one you want
Solution files parsed on first expand Expanding a solution is the first thing the tree parses
Children resolved per node and cached A 300-project solution reads 0 projects until you expand one
One readdir per folder, no stat except for symlinks Folder expansion is a single syscall pass
Project models cached by mtime + size Re-expanding a project re-parses nothing
Project kind read from the file's first 4 KB, after its solution is drawn Web projects get their own icon without a read-everything pass
File watchers created after the scan, not at activation The first paint is never behind watcher setup
Targeted invalidation via a directory index A new file redraws one folder, not the tree
Debounced watcher (100 ms), create/delete only Typing in a file never touches the tree
The TODO sweep waits until that section is opened Never paid by anyone who does not use it
Saving a file re-reads that one file, not the workspace The TODO list stays current for free

On a four-solution, 47-project workspace the first solutions appear 4 ms into the scan and the whole workspace is walked in 46 ms - and on every later open the remembered solutions are on screen before the scan even starts.

Settings

Setting Default Meaning
csharpSolutionExplorer.autoReveal true Select the active editor's file in the tree
csharpSolutionExplorer.showDependencies true Show the Dependencies node under each project
csharpSolutionExplorer.showProjectsWithoutSolution true Fall back to project roots when no solution exists
csharpSolutionExplorer.excludedFolders ["bin", "obj", ".vs", ".git", "node_modules"] Folder names skipped by the scan and hidden inside projects
csharpSolutionExplorer.maxScanDepth 10 Directory levels searched below each workspace folder
csharpSolutionExplorer.todoTags ["TODO", "FIXME", "HACK"] Comment tags collected into the TODO section
csharpSolutionExplorer.todoExtensions [".cs", ".razor", ".cshtml"] File extensions searched for those tags
csharpSolutionExplorer.openPreview true Open files in preview mode on single click
csharpSolutionExplorer.buildConfiguration Debug Configuration used by Build, Clean, Rebuild, Run and Debug until one is chosen in the view
csharpSolutionExplorer.publishConfiguration Release Configuration the Publish picker starts on
csharpSolutionExplorer.buildArguments [] Extra arguments appended to dotnet build, one per entry. Clean is left alone
csharpSolutionExplorer.publishArguments [] Extra arguments appended to dotnet publish, one per entry
csharpSolutionExplorer.treeLineColor "" Colour of the tree's indent guides as a hex colour, mirrored into workbench.colorCustomizations. Empty leaves the colour theme's own

Commands

All 42, and where each one is. Most belong to a node, so they are on a context menu and hidden from the command palette - offering Rename... with nothing selected would mean nothing.

The tree

Command Where
Refresh Solutions title bar, command palette
Reveal Active File Solutions title bar, command palette
Tree Line Colour... Solutions view menu, command palette
Open File The click that opens a file

Creating

Command Where
Add New Project... Solution, solution folder
Add New Item... Project, folder
Add Directory... Project, folder
Add Scratch File... Project, folder

References, diagrams and readers

Command Where
Add Project Reference... Project
Remove Project Reference... Project
Project Reference Diagram Solution, solution folder, project
Package Reference Diagram Solution, solution folder, project
Endpoints Explorer Solution, solution folder, project
Dead Code Explorer Solution, solution folder, project
Manage NuGet Packages... Solution, solution folder, project, any Dependencies node, Solutions title bar, command palette

Bookmarks and TODO

Command Where
Add Bookmark File, folder, the editor's context menu, command palette
Remove Bookmark A bookmark, inline and on its context menu
Remove All Bookmarks Bookmarks title bar, command palette
Open Bookmark The click that opens a bookmark
Search Again TODO title bar, command palette
Open TODO The click that opens a TODO

Files and folders

Command Where
Reveal in File Explorer File, folder, project, solution
Open in Integrated Terminal Folder, project, solution
Copy Path File, folder, project, solution
Copy Relative Path File, folder, project, solution
Find in Folder... Folder, project, solution
Cut Copy Paste Delete Rename... The Edit submenu on any file or folder. Paste appears only once something has been cut or copied

Run and build

Command Where
Build Solution, project, Run Configurations title bar and Actions, command palette
Rebuild Solution, project, Run Configurations Actions, command palette
Clean Solution, project, Run Configurations Actions, command palette
Publish... Solution, project, Run Configurations Actions, command palette
Select Build Configuration... The Configuration row, command palette
Run The editor's title bar, Run Configurations title bar, command palette
Debug The editor's title bar, Run Configurations title bar, command palette
Set as Startup Project... Project, the Startup Project row, command palette
Use Launch Profile The click on a launch profile row
Edit launchSettings.json Run Configurations Actions, command palette
Refresh Run Configurations Run Configurations title bar, command palette

Everything that runs the .NET SDK - Add New Project..., the NuGet operations, and Build, Clean, Rebuild, Publish, Run and Debug - is offered in a trusted workspace only.

Add New Project...

On the context menu of a solution or solution folder. It picks a template from dotnet new list --type project, asks for a name, runs dotnet new into the folder you right-clicked, then dotnet sln add --solution-folder so the project lands in that same solution folder. It needs the .NET SDK on PATH and a trusted workspace; without an SDK it offers C# Dev Kit's .NET: New Project... instead.

Solution folders are virtual, so the directory is inferred: the matching path below the solution when one exists (src -> <solution>/src), otherwise the directory the projects already filed there share, otherwise the solution's own directory.

Add New Item...

On the context menu of a project or any folder inside one. Pick Class, Interface, Record, Struct or Enum, give it a name, and the file is written straight to disk - no SDK, no process, no wait.

  • Namespace is <RootNamespace> (or the project file's name) plus the folders between the project and the file, folded into identifiers the way MSBuild folds them: My Folder/2Do -> My_Folder._2Do.
  • File-scoped or block-scoped follows csharp_style_namespace_declarations from the nearest .editorconfig. Without one it is file-scoped only when every target framework is .NET 6 or later, since that is where C# 10 begins.
  • Legacy projects get a matching <Compile Include="..."/> next to the last one in the project file; commented-out items are skipped, and a project whose shape is unclear is reported rather than rewritten.
  • Types are declared internal, which is what Visual Studio writes.

Add Directory... and Add Scratch File...

Directly below Add New Item..., on the same nodes.

Add Directory... makes a folder; Add Scratch File... makes an empty file of whatever kind you name - notes.md, Dockerfile and .gitignore all work, since the extension is taken verbatim. Both accept a sub-path, so Models/Dto creates two levels at once, and neither will climb out of the folder you clicked.

In a legacy project the new file is registered too: <Compile> for .cs, <None> for everything else. An empty folder has no representation in a project that lists its files one by one, so that case is reported rather than silently doing nothing.

Edit

An Edit submenu on every file and folder: Cut, Copy, Paste, Delete, Rename....

Paste only appears once something has been cut or copied, and refuses to paste a folder into itself. A name already taken gets copy appended, then copy 2. A cut across volumes falls back to a copy and a delete, which a plain rename cannot do.

Rename goes through a workspace edit rather than a bare rename, so language extensions get their chance to follow the file and update namespaces and references. Delete confirms first and goes to the recycle bin, falling back to a permanent delete only where there is no recycle bin.

Legacy projects keep up throughout: renaming or moving a file rewrites its item, deleting removes it, and doing either to a folder carries every item below it along. Wildcard includes and commented-out items are left alone.

Adding and removing project references

Add Project Reference... and Remove Project Reference... on a project. Both open a tick list, so several go in or out at once, and the project file is edited in a single pass.

A project reference carries its own references with it - reference B, and everything B references arrives too. Only the projects worth adding get a checkbox; the rest are shown with the reason and no way to tick them, since quick pick draws a separator without one:

Row Meaning
ticked box, brings N more with it how much else arrives along with this one
no box, arrives through X already in reach, so there is nothing to add
no box, already references this project referencing it back would close a loop
would still arrive through X on removal - another reference still carries it, so nothing is lost

New references join the item group the existing ones live in; a group left empty is taken out with them. Commented-out references are left alone.

Project reference diagrams

On the context menu of a solution, a solution folder, or a project. It reads every <ProjectReference> in scope and opens two tabs, because no one diagram answers everything about a dependency graph.

Reference Graph - a layered node-link drawing. A project sits one layer below everything that references it, so applications end up along the top and the shared libraries settle at the bottom; the number of layers is the depth of the stack. Cycles are found first and the edges that close them are drawn dashed and red, so the rest can still be ranked. Within a layer, nodes are ordered by repeated median sweeps, the standard cheap way to pull most of the crossings out. Hovering a project dims everything it does not touch; clicking opens it. Drag to pan, scroll to zoom.

This is the reading people expect of a dependency diagram, and it is the better of the two up to something like a hundred projects. Past that the edges become a hairball.

Reference Matrix - the same edges as a grid, which never becomes a hairball however many projects there are. Rows and columns are in layer order, so an acyclic solution fills only the lower triangle and any mark above the diagonal is a cycle. A dense row is a project that depends on everything; a dense column is one everything depends on. Fan-in and fan-out totals run along the edges, and the number beside each row is its layer.

Both views count what they found and say whether the graph is acyclic. References to projects outside the solution are kept and marked rather than dropped.

Both carry the same filter bar: by name, by Kind - library, web, or referenced from outside the solution - and by In a cycle, which keeps only the projects sitting on a reference that closes one. + neighbours widens a match back out to what it references and what references it, which is usually the question being asked of one project. The graph hides what is filtered out and refits around what is left; the matrix drops the row and the column together and re-counts, since a fan-out still counting projects the filter had taken away would be a number that lies.

Package reference diagrams

Package Reference Diagram, on the same nodes, reads every <PackageReference> in scope and opens two more tabs. Package references make a bipartite graph - projects on one side, packages on the other, and no package depends on another, because a project file states only what it asks for directly. That different shape wants different drawings.

Package Graph - two columns, projects on the left with the number of packages each pulls in, packages on the right with the number of projects using each. Laid out in rows a solution's worth of packages runs thousands of pixels wide; in columns it reads top to bottom like a list. A few barycentre sweeps keep the edges between the columns from becoming a mesh. Hover either column to isolate what it touches.

Package Matrix - packages as rows, projects as columns, ordered so the widely shared packages are at the top. A package's versions are spelled out beside its name, which is the room the row header has and the column does not. A package used at more than one version turns red, and its cells name the version each project is on - the answer worth having, and the one nothing else in the tree will tell you.

Both filter by name across the two columns, by a single project, and by Conflicting or Shared - packages used at more than one version, and packages used by two or more projects. The package graph closes its two columns up around what is left and redraws every line between the new positions, so a filtered drawing looks like a drawing of that size rather than a full one with holes in it.

Endpoints Explorer

Endpoints Explorer, on a solution, a solution folder or a project. It reads every attribute-routed controller action and every minimal API in scope and opens them as a list you can send a request from.

Opening a row gives you a form, the shape Swagger gives one. Every value the action binds is listed - path, query, header, form - each with its C# type, a red asterisk where it is required, a picker where the type is an enum or a bool, and the default from the signature as the placeholder, so an empty box means the server's own default applies. [FromQuery] Filter filter is listed as one input per property, flattened the way the binder reads it back, and [FromHeader(Name = "X-Tenant")] goes out as that header rather than as a query key. The URL under the form is assembled as you type, with any token still unfilled shown in red; Send will not go until the required ones are filled, and marks the boxes that are missing.

A body has an example in it before you touch it, shaped from the DTO the action takes: every property of every nested record and list, with a placeholder of the right JSON type, and Example and Format buttons beside Send. Records, positional records, classes, structs and enums are read across the whole solution, so a type declared in another project still shapes the payload. [FromForm] values are sent url-encoded instead of as JSON.

Nothing is compiled: the signature, the route template and the DTOs are read as text, on the same pass over the files the explorer already made, and the base URLs come from launchSettings.json. Where the reader cannot work something out, the row keeps an extra query box to type it in by hand.

Dead Code Explorer

Dead Code Explorer, on the same nodes. It counts what every declared type is mentioned by and reports the ones nothing mentions.

The counting is textual, and that sets the shape of the whole thing. A reference count cannot see reflection, dependency injection by convention, or a caller in another solution, so a finding is a lead and never a verdict - the page says so in its banner and against every row that has a reason to doubt it. The analysis is deliberately biased towards saying nothing: every route by which something could be reached without being named - a base list, a string literal, an attribute, a controller the framework routes to - is recorded and used to hold a finding back or mark it down, never to push one forward.

Confidence Meaning
high Nothing suggests anything else can reach it
medium It could be reached without being named - it is public, it carries an attribute, or it is an interface only its implementations know
low Something almost certainly reaches it another way: a controller the framework routes to, or a name that appears in a string literal

Findings group by category - Controller, Service, DTO, Interface, Enum, Class - or by confidence, by project, or not at all, and the bar filters by name, by project and by confidence. A row says which project and file it is in, how many types derive from it, and whether it is the only declaration in its file, which is the difference between deleting a type and deleting a file. Clicking a name opens it.

A type's own file is discounted, since a class used only by itself is exactly what this is looking for. Generated files are not reported, and test projects are read for their mentions even where their own declarations are not - a mention is a mention wherever it comes from.

NuGet Package Manager

Manage NuGet Packages... on a solution, a solution folder, a project or anything under Dependencies - and on the Solutions title bar. Right-clicking one package under Dependencies opens the window already on it.

Two tabs, laid out the way Rider's NuGet tool window is laid out - the list on the left, the selected package on the right, and a divider you can drag between them.

Packages

Installed opens on what your project files already say, so the list is on screen before a single request goes out. Above it sits Packages to Update in Solution: N, filled in as the answers arrive from the feed: each row carries the version you are on and the version on offer, and the checkboxes plus the group's Update button move as many of them as you like in one go. Below that, the packages that are already current. Discover searches your feeds instead, with downloads, a reserved-prefix tick and a mark against anything you already reference.

On the right: a version dropdown carrying every published version, Info (description, authors, licence, publication date, links to the project and the gallery), Frameworks and Dependencies read from the registration leaf for the exact version selected, and then the projects in scope. Each project row installs with + or removes with the bin - so one package can go into one project without touching the rest.

Which feeds are searched comes from the whole NuGet.Config chain that applies to the solution - <clear/>, <disabledPackageSources> and the nearest file winning are all honoured - and the dropdown lists the ones that can actually be queried.

Health

Deliberately not another package browser. It is a solution-wide report answering the four questions that get harder as a solution grows.

Drift is the quiet one. Three projects on three versions of Microsoft.Extensions.Logging compiles fine and then produces a binding surprise at run time, and nothing else in the tooling says so. It is a matrix - package per row, project per column - so a row whose versions do not match is wrong at a glance, with nothing to read.

Vulnerable and deprecated packages sort to the top with a severity badge. Transitive origin arrives on expand: the direct reference that pulled the package in, which is the actual question when a transitive dependency is flagged. That chain is read out of obj/project.assets.json, the only file that knows it.

Three bulk actions and a migration sit along the bottom, each showing exactly what it would do before it does it:

Action What it does
Fix vulnerabilities Moves every vulnerable package to the newest published version. A vulnerable transitive package is named, not touched - it says which direct reference to update instead.
Unify drifted rows Brings every project on to the highest version any of them already uses. Also on a single row, from its expander.
Update patch versions Only where major and minor already match.
Migrate to Central Package Management Lifts every version into Directory.Packages.props and drops the Version attribute from each project.

The migration is the risky one, so it is built to be refused. It will not start on a dirty working tree - a clean tree is the undo. It shows a real diff of every file, including the props file it would create, before anything is written. It leaves alone, and reports, everything the props file cannot express on its own: a reference in a conditional ItemGroup, a VersionOverride, a floating 8.0.*, a version that comes from an MSBuild property, an Update rather than an Include. PrivateAssets and every other metadata stay exactly as they were. And once applied, Undo migration puts every file back.

Not all drift is a defect - a test project on a newer analyser is fine - so any package can be muted from its expander. Muted packages stop counting, and the mute is remembered with the workspace.

How a change is made

Nothing here edits your project file, the CPM migration aside. Every change is dotnet add package or dotnet remove package, sent to a terminal named NuGet that opens at the solution and is reused - so you see the command, you see the restore, and you see it fail if it fails. Paths are relative to that terminal, which keeps the transcript to something you could have typed yourself. More than three projects at once, or removing from several, asks first. When the SDK finishes, the project file it wrote is picked up by a watcher and the list redraws itself.

Central Package Management is understood throughout: a project that names a package with no version is matched against Directory.Packages.props, the version it contributes is shown, and the row is marked central.

What it costs

The Packages tab opens on cached project files and no network at all. The update check that follows is one static index.json per package from the feed's package base address - the cheapest question a feed answers - eight in flight at a time, each posted into the list as it lands, so fifty packages fill in progressively instead of waiting on the slowest one. Answers are cached with a TTL (feed index 30 min, versions 5 min, search 2 min) and de-duplicated while in flight, so typing in the search box never sends a request that is already on its way back. Searching is debounced and a superseded search is dropped rather than rendered.

The Health tab costs three dotnet list package invocations, so it does not run until the tab is opened, and they go out together rather than one after another. Everything they cannot answer degrades rather than failing: an unreachable feed, a private feed the SDK has no credentials for, an SDK too old for --format json, or a solution that was never restored still gets the drift analysis, which is computable from the project files alone. The banner says which of those happened, and offers to run the restore when that is the answer. A feed waiting on a sign-in prompt a child process can never show is given a time limit and then explained rather than left to hang.

The page draws its own package icons from the package id, so a solution's worth of rows costs no image requests, and the whole view holds still under default-src 'none' with a nonce - no remote script, font or stylesheet anywhere in it. The matrix keeps its package column pinned while the projects scroll, shows only the columns the visible rows actually use, and defaults to Problems only, which is what keeps a sixty-project solution legible.

Changing packages runs the .NET SDK, so it needs a trusted workspace. Reading feeds is the one place this extension talks to the network, and only while the window is open.

Bookmarks

Add Bookmark sits on files and folders in Solutions, and on the editor's own context menu - which is the only place a method exists to be marked.

In the editor it marks whatever encloses the cursor: the method, property or constructor, named with the type it lives in. The symbol comes from whichever language extension is serving the file, so a C# method needs the C# extension awake; with no answer the line itself is marked, which is never wrong.

Bookmarks are kept in workspace state, so they come back when the workspace does, and they follow a file that is renamed or moved - including a file moved along with its folder.

TODO

Every TODO, FIXME and HACK comment in the workspace, grouped by file, with the tag and line beside each.

The sweep does not start until the section is opened, results stream in as files are read, and saving a file re-reads only that file. On a 3,600-file, 17 MB workspace the whole sweep takes ~590 ms, of which the directory walk is 67 ms.

A tag has to follow a comment opener (//, /*, *, <!--, #), so "TODO: ..." inside a string literal is left alone.

Run Configurations

One startup project, the launch profiles it declares, and what you can do with them:

Startup Project      Api
Configuration        Debug
Launch Profiles      3
  http
  https              active
  IIS Express        IISExpress
Actions
  Build              Debug
  Rebuild            Debug
  Clean              Debug
  Publish...
  Set as Startup...
  Edit launchSettings.json

Run and Debug sit in the top right of the editor, the way Visual Studio's toolbar carries them, and on the section's own title bar - a green triangle and a blue bug, in colour rather than the toolbar's grey. They appear as soon as a startup project is known - instantly in a workspace that has chosen one before, since that is remembered in workspace state and costs no disk read to restore.

The extension wakes on a workspace holding a solution or project file, so the buttons are there without the Solution Explorer ever having been opened. Where no project has been chosen yet, the search for one waits until the window has painted.

The startup project is remembered in workspace state. When the workspace holds a single .sln or .slnx, one is chosen for you the first time it is needed - the first web project, else the first executable, else the first project the solution lists. With several solutions nothing is guessed; pick one with Set as Startup Project..., which is also on the context menu of any project under Solutions.

Profiles come from the project's Properties/launchSettings.json, in the order the file lists them. The active profile is the one you clicked, or the first commandName: "Project" profile. Profiles the .NET CLI cannot start - IISExpress, Docker - are shown with their command name and say so if you try. Where the project has no launchSettings.json the action turns into Create launchSettings.json and writes a minimal one.

Run starts dotnet run --project <project> --launch-profile <profile> as the terminal's own process, so nothing is quoted through a shell and the app still reads stdin. Re-running replaces the terminal.

Debug builds the project, finds what the build produced under bin/<configuration>, and hands VS Code a coreclr launch configuration carrying the profile's environment, arguments, working directory and applicationUrl - so it needs the C# extension for the debugger itself. A console app gets the integrated terminal, a web app the internal console and a browser opened on the URL it reports.

Reading this section costs the solution file the tree would have read anyway plus one launchSettings.json. No project file is read to draw it, and nothing is spawned until Run or Debug is clicked.

Build, Clean, Rebuild and Publish

All four are on the context menu of any solution or project under Solutions, in the Run Configurations section for the startup project, and in the command palette - where, with nothing to go on, they ask which solution or project you mean.

They run as VS Code tasks with the $msCompile problem matcher attached, which is the point: every MSBuild warning and error becomes a Problems entry you can click through to the line. The SDK is started as a process, not typed into a shell, so a path with a space in it cannot come apart on the way.

Command What runs
Build dotnet build <target> -c <configuration>
Clean dotnet clean <target> -c <configuration>
Rebuild Clean, then Build - Visual Studio's Rebuild, spelled out, the build only starting if the clean succeeded
Publish dotnet publish <target> with whatever the picker collected

Configuration

One configuration is in force at a time, shown on the Configuration row of the Run Configurations section and used by Build, Clean, Rebuild, Run and Debug alike. Click the row - or run Select Build Configuration... - to change it, and the choice is remembered in workspace state. Until you pick one, csharpSolutionExplorer.buildConfiguration decides.

The list offered is Debug, Release, and anything else the target names: <Configurations> in a project file, SolutionConfigurationPlatforms in a .sln, <BuildType> in a .slnx. Nothing is evaluated - it is one read of a file the tree has usually read already.

Publish

The first pick is the whole thing for most publishes: a configuration, or Publish Again with the options that target last used. More Options... opens the rest, and each step only appears where the project leaves it open:

Step Offered
Configuration The same list the Configuration row uses
Target framework Only when the project multi-targets
Runtime identifier Portable, the project's own <RuntimeIdentifiers>, then the common RIDs
Deployment mode Only when a runtime was chosen: framework-dependent or self-contained
Output folder The default bin/<configuration>/<framework>/publish, or one you browse to

The answers are remembered per target, and every step starts on what was chosen last time, so a repeat publish is one Enter per step. When it finishes, the notification carries the output folder with Reveal in File Explorer and Copy Path - for a project, where the SDK put it; a solution publishes each of its projects into a folder of its own, so there it just says it is done.

csharpSolutionExplorer.buildArguments and csharpSolutionExplorer.publishArguments append anything else you need (-v:n, -p:PublishSingleFile=true) to dotnet build and dotnet publish respectively.

Tree line colour

Tree Line Colour..., on the Solutions view menu. It offers the palette the extension's own icons are drawn in - solution purple, project green, web blue, package violet, amber - or any hex colour you like, written as #RGB, #RGBA, #RRGGBB or #RRGGBBAA. csharpSolutionExplorer.treeLineColor is the setting behind it.

One thing to know before turning it on: VS Code draws every tree in the window from a single pair of colours, tree.indentGuidesStroke and tree.inactiveIndentGuidesStroke, and an extension cannot scope either to its own view. So the setting is mirrored into your workbench.colorCustomizations, and the File Explorer, Search and Debug trees get the same coloured lines.

It merges rather than replaces, so your other colour customizations are untouched; clearing the setting takes the two values back out; and a line colour you have since changed by hand is left exactly as you set it, because at that point it is yours and not ours to revoke.

Development

npm install
npm run watch     # esbuild in watch mode

Then press F5 to launch an Extension Development Host.

npm run typecheck # tsc --noEmit
npm run build     # production bundle into dist/
npm run smoke     # run the parsers against fixtures/ and print the parsed model
npm run nuget     # check the NuGet side: versions, NuGet.Config, central versions, command lines
npm run nuget -- --live            # the same, plus a round trip to nuget.org
npm run tree -- path/to/Some.sln   # print a real solution the way the tree renders it
npm run endpoints # the endpoint reader and the explorer page
npm run diagrams  # the four diagram pages and their filters
npm run lines     # what the line colour writes to your settings
npm run icon      # re-render media/icon.png from media/activity-bar.svg
npm run package   # build a .vsix

fixtures/ holds a small workspace covering the interesting shapes: a classic .sln with nested solution folders and solution items, the same solution as .slnx, a second solution in a subfolder, an SDK-style project with package and project references, and a legacy non-SDK project with explicit compile items.

Icons

Everything the extension draws in the UI ships as SVG in media/ and needs no build step - the activity bar logo included. The one exception is the marketplace logo, which the gallery wants as a raster: scripts/gen-icons.ps1 copies the authored media/source/logo.png into media/icon.png and checks it against what vsce will accept - at least 128x128, square, and a real PNG. media/source/ holds the original design files and is not shipped (.vscodeignore), so the copy in media/ is the one that goes into the package.

npm run icon
Artwork Used for
media/activity-bar.svg The activity bar logo that opens the view
media/solution.svg Solution nodes
media/solution-folder.svg Solution folder nodes
media/project.svg Project nodes
media/api-project.svg Projects built on Microsoft.NET.Sdk.Web
media/folder.svg Folder nodes, overriding the file icon theme
media/run.svg The Run button, in the editor's title bar and the Run Configurations title bar
media/debug.svg The Debug button, in the same two places
media/icon.png Marketplace logo - the only raster, installed from media/source/logo.png by npm run icon

Run and Debug are SVG files rather than codicons on purpose: a codicon is a font glyph and always takes the theme's icon foreground, so $(play) cannot be green. A file-based icon keeps its own colour, which is what puts a green triangle and a blue bug in the title bar instead of two grey shapes.

Files keep whatever the active file icon theme gives them, so C# files still look like C# files.

Layout

src/
  extension.ts        activation, tree view, auto reveal
  commands.ts         command handlers
  config.ts           memoized settings
  model/nodes.ts      the node union rendered by the tree
  parse/sln.ts        classic .sln reader (single pass, line based)
  parse/slnx.ts       .slnx reader (tag scanner)
  parse/project.ts    .csproj reader + mtime-keyed cache
  fs/dir.ts           directory reads
  fs/scan.ts          breadth-first workspace scan that reports as it goes
  tree/provider.ts    TreeDataProvider: lazy children, caching, invalidation, reveal
  tree/lines.ts       the indent guide colour, mirrored into the workbench colours
  util/csharp.ts      reading C# as text: comments and string literals blanked in place
  util/paths.ts       MSBuild path resolution, case-aware keys, natural-order sorting
  edit/clipboard.ts   what was cut or copied, and the paste that follows
  edit/fileOps.ts     cut, copy, paste, delete and rename over workspace edits
  edit/references.ts  add and remove project references, and what each one brings
  scaffold/target.ts     the folder a new item lands in, and the names it accepts
  scaffold/namespace.ts  the namespace a folder implies, folded the way MSBuild folds it
  scaffold/templates.ts  the Class, Interface, Record, Struct and Enum bodies
  scaffold/newItem.ts    Add New Item...
  scaffold/newEntry.ts   Add Directory... and Add Scratch File...
  scaffold/projectItems.ts  the <Compile>/<None> item a legacy project needs
  dotnet/cli.ts       the SDK as a child process, and the output channel it writes to
  dotnet/templates.ts the template list read from dotnet new list
  dotnet/newProject.ts   Add New Project..., and filing it in the solution folder
  bookmarks/store.ts     bookmarks in workspace state, following renames and moves
  bookmarks/commands.ts  marking a folder, a file, or the symbol under the cursor
  bookmarks/provider.ts  the Bookmarks section
  todo/model.ts       a tag, its line and the file it came from
  todo/provider.ts    the TODO sweep, streamed, and the re-read on save
  diagram/graph.ts    the project reference graph in scope, cycles included
  diagram/packages.ts the bipartite project-to-package graph
  diagram/layout.ts   layering and median sweeps, the crossings taken out
  diagram/html.ts     page furniture shared by all four, so they read as one family
  diagram/render.ts   the reference graph and the reference matrix
  diagram/renderPackages.ts  the package graph and the package matrix
  diagram/panels.ts   the four tabs and the messages they answer
  deadcode/analyze.ts declarations, mentions, base lists and names in string literals
  deadcode/scan.ts    the sweep over a solution, and what discounts a mention
  deadcode/model.ts   categories and the three confidences
  deadcode/render.ts  the report page, grouped and filtered
  deadcode/panel.ts   the tool window
  nuget/model.ts      package vocabulary + SemVer 2 comparison
  nuget/sources.ts    the NuGet.Config chain: which feeds, and which are enabled
  nuget/client.ts     NuGet V3 over node:https, cached and de-duplicated
  nuget/scope.ts      installed packages per project, incl. Directory.Packages.props
  nuget/health.ts     drift, vulnerabilities, deprecation via dotnet list package
  nuget/assets.ts     project.assets.json: what pulled a transitive package in
  nuget/migrate.ts    Central Package Management migration: plan, diff, apply, undo
  nuget/ops.ts        dotnet add/remove package in a reused terminal
  nuget/panel.ts      the tool window: feeds in, SDK out, watcher for the result
  nuget/render.ts     the page itself
  run/target.ts       the projects the run view can start, from the solution alone
  run/model.ts        launchSettings.json for the startup project, and nothing else
  run/store.ts        the startup project and its launch profile, in workspace state
  run/provider.ts     the Run Configurations section
  run/commands.ts     Run, Debug, startup project, launch profiles
  build/model.ts      build targets and the configurations they name
  build/store.ts      the active configuration, and what publish was last asked for per target
  build/tasks.ts      dotnet as a VS Code task, with the $msCompile matcher
  build/publish.ts    the publish picker, its arguments and its output folder
  build/commands.ts   Build, Clean, Rebuild, Publish
  endpoints/model.ts  verbs, parameters and the DTO shapes behind them
  endpoints/parse.ts  attribute-routed controllers and minimal APIs, read as text
  endpoints/bind.ts   C# signatures: which value goes in the path, query, header or body
  endpoints/schema.ts payload examples and query models flattened into fields
  endpoints/scan.ts   the sweep over a solution, and the URLs in launchSettings.json
  endpoints/http.ts   the try-it request, made from the extension host
  endpoints/panel.ts  the tool window and the messages it answers
  endpoints/render.ts the page itself
media/                icon artwork, SVG except the marketplace logo
scripts/
  gen-icons.ps1       renders media/icon.png from media/activity-bar.svg
  smoke.ts            parser harness run by npm run smoke
  nuget-probe.ts      NuGet checks run by npm run nuget
  sln-tree.ts         prints a real .sln as the tree renders it (npm run tree)
  endpoint-probe.ts   endpoint reader and explorer page checks (npm run endpoints)
  diagram-probe.ts    runs the four diagram pages and works their filters (npm run diagrams)
  lines-probe.ts      what the line colour writes to your settings (npm run lines)
  bench-view.ts       tree open and expand timings against a real workspace
  vscode-stub.ts      enough of the vscode API to run the tree in a plain Node process

Roadmap

Search within a solution, and a Test Explorer over dotnet test.

License

MIT

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft