Extends Visual Studio's Open Folder mode with custom file scanners, file context providers, file actions, and workspace settings for non-MSBuild projects.
How this skill is triggered — by the user, by Claude, or both
Slash command
/vs-extensibility-skills:extending-open-folderThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Open Folder lets users open any codebase in Visual Studio without a project or solution file. Extensions can enhance this experience by providing:
Open Folder lets users open any codebase in Visual Studio without a project or solution file. Extensions can enhance this experience by providing:
.vs/VSWorkspaceSettings.jsonAll Open Folder APIs are in the Microsoft.VisualStudio.Workspace.* namespaces and are MEF-based.
Open Folder extensibility matters when your extension needs to work outside the traditional project/solution model — for example, supporting lightweight editing of Python, Rust, Go, or JavaScript projects that don't use MSBuild. Without Open Folder providers, VS treats these folders as inert file trees with no build, navigation, or debug support.
When to use Open Folder vs. alternatives:
Not supported. The new extensibility model does not have Open Folder workspace APIs. The Microsoft.VisualStudio.Workspace.* APIs require in-process MEF composition. If your VisualStudio.Extensibility extension needs Open Folder support, use an in-process hybrid component.
The Toolkit and VSSDK approaches are identical for Open Folder — all workspace extensibility uses the same MEF-based Microsoft.VisualStudio.Workspace.* APIs.
NuGet packages:
Microsoft.VisualStudio.SDK (≥ 17.0)Microsoft.VisualStudio.Workspace.Extensions (contains the workspace API types)Key namespaces:
Microsoft.VisualStudio.WorkspaceMicrosoft.VisualStudio.Workspace.BuildMicrosoft.VisualStudio.Workspace.Extensions.VSMicrosoft.VisualStudio.Workspace.IndexingMicrosoft.VisualStudio.Workspace.SettingsMyExtension/
├── OpenFolder/
│ ├── MyFileContextProvider.cs
│ ├── MyFileActionProvider.cs
│ ├── MyFileScanner.cs
│ └── MySettingsProvider.cs
├── MyExtensionPackage.cs
├── source.extension.vsixmanifest
└── MyExtension.csproj
Open Folder providers are discovered through MEF. Add the MefComponent asset to your source.extension.vsixmanifest:
<Assets>
<Asset Type="Microsoft.VisualStudio.MefComponent" d:Source="Project" d:ProjectName="%CurrentProject%" Path="|%CurrentProject%|" />
</Assets>
File context providers supply metadata about files — build contexts, debug contexts, or custom data. The workspace matches consumer requests to providers based on the context type GUID.
OpenFolder/MyFileContextProvider.cs:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Workspace;
namespace MyExtension.OpenFolder;
// The factory creates a provider instance per workspace
[ExportFileContextProvider(
ProviderType,
PackageIds.BuildContextTypeGuid)] // The context type GUID this provider produces
internal class MyBuildContextProviderFactory : IWorkspaceProviderFactory<IFileContextProvider>
{
private const string ProviderType = "9A31D832-5AB2-4E1A-A446-5B4E5AC58A3A";
public IFileContextProvider CreateProvider(IWorkspace workspace)
{
return new MyBuildContextProvider(workspace);
}
}
internal class MyBuildContextProvider : IFileContextProvider
{
private readonly IWorkspace workspace;
public MyBuildContextProvider(IWorkspace workspace)
{
this.workspace = workspace;
}
public async Task<IReadOnlyCollection<FileContext>> GetContextsForFileAsync(
string filePath,
CancellationToken cancellationToken)
{
// Only provide context for .mylang files
if (!filePath.EndsWith(".mylang", StringComparison.OrdinalIgnoreCase))
{
return Array.Empty<FileContext>();
}
var context = new FileContext(
new Guid("9A31D832-5AB2-4E1A-A446-5B4E5AC58A3A"), // Provider GUID
BuildContextTypes.BuildContextType, // Context type
filePath,
new[] { filePath });
return new[] { context };
}
}
| Constant | Purpose |
|---|---|
BuildContextTypes.BuildContextType | Build contexts for the Build menu |
BuildContextTypes.RebuildContextType | Rebuild contexts |
BuildContextTypes.CleanContextType | Clean contexts |
File context actions add entries to the right-click context menu for files in Solution Explorer (Open Folder mode).
OpenFolder/MyFileActionProvider.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Extensions.VS;
namespace MyExtension.OpenFolder;
[ExportFileContextActionProvider(
(FileContextActionProviderOptions)0, // No special options
ProviderType,
PackageIds.BuildContextTypeGuid)] // Apply to build contexts
internal class MyFileActionProviderFactory : IWorkspaceProviderFactory<IFileContextActionProvider>
{
private const string ProviderType = "B1C3F8A5-2D4E-4F6A-8B9C-1E2D3F4A5B6C";
public IFileContextActionProvider CreateProvider(IWorkspace workspace)
{
return new MyFileActionProvider();
}
}
internal class MyFileActionProvider : IFileContextActionProvider
{
public Task<IReadOnlyList<IFileContextAction>> GetActionsAsync(
string filePath,
FileContext fileContext,
CancellationToken cancellationToken)
{
var actions = new List<IFileContextAction>
{
new WordCountAction(filePath)
};
return Task.FromResult<IReadOnlyList<IFileContextAction>>(actions);
}
}
internal class WordCountAction : IFileContextAction
{
private readonly string filePath;
public WordCountAction(string filePath)
{
this.filePath = filePath;
}
public string DisplayName => "Count Words";
public async Task<IFileContextActionResult> ExecuteAsync(
IProgress<IFileContextActionProgressUpdate> progress,
CancellationToken cancellationToken)
{
string text = File.ReadAllText(filePath);
int wordCount = text.Split(
new[] { ' ', '\t', '\r', '\n' },
StringSplitOptions.RemoveEmptyEntries).Length;
// Show result — use VS.MessageBox if using Toolkit, or
// IVsUIShell message box for pure VSSDK
System.Windows.Forms.MessageBox.Show(
$"Word count: {wordCount}",
"Word Count",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Information);
return new FileContextActionResult(true);
}
}
File scanners extract symbol information from custom file types so they appear in Go To (Ctrl+,) and Navigate To.
OpenFolder/MyFileScanner.cs:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Indexing;
namespace MyExtension.OpenFolder;
[ExportFileScannerProvider(
ProviderType,
"File")] // Scanner attribute type
internal class MyFileScannerProviderFactory : IWorkspaceProviderFactory<IFileScanner>
{
private const string ProviderType = "D4E5F6A7-8B9C-1D2E-3F4A-5B6C7D8E9F10";
public IFileScanner CreateProvider(IWorkspace workspace)
{
return new MyFileScanner();
}
}
internal class MyFileScanner : IFileScanner
{
// Declare which kinds of data this scanner produces
public IReadOnlyCollection<FileDataValue> ReadableFileDataTypes { get; } = new[]
{
FileDataValue.FileSymbolType
};
public async Task<T[]> ReadFileAsync<T>(string filePath, CancellationToken cancellationToken)
where T : class
{
// Only scan .mylang files
if (!filePath.EndsWith(".mylang", StringComparison.OrdinalIgnoreCase))
{
return Array.Empty<T>();
}
if (typeof(T) != typeof(FileSymbol))
{
return Array.Empty<T>();
}
var symbols = new List<FileSymbol>();
string[] lines = await Task.Run(() => File.ReadAllLines(filePath), cancellationToken);
// Simple pattern: lines starting with "func " define a function symbol
var funcPattern = new Regex(@"^func\s+(\w+)", RegexOptions.Compiled);
for (int i = 0; i < lines.Length; i++)
{
var match = funcPattern.Match(lines[i]);
if (match.Success)
{
symbols.Add(new FileSymbol(
match.Groups[1].Value, // Symbol name
SymbolKind.Function,
new TextSpan(i, 0, i, lines[i].Length),
filePath));
}
}
return symbols.Cast<T>().ToArray();
}
}
Open Folder stores per-folder settings in .vs/VSWorkspaceSettings.json. Extensions can read and write these settings.
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Settings;
private void ReadSettings(IWorkspace workspace)
{
IWorkspaceSettingsManager settingsManager = workspace.GetSettingsManager();
IWorkspaceSettings settings = settingsManager.GetAggregatedSettings(SettingsTypes.Generic);
// Read typed values
WorkspaceSettingsResult result = settings.GetProperty("myext.maxItems", out int maxItems);
if (result == WorkspaceSettingsResult.Success)
{
// Use maxItems
}
// Extension method with default
string outputDir = settings.Property("myext.outputDir", /* default */ "bin");
}
Implement IWorkspaceSettingsProviderFactory to provide settings programmatically:
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Workspace;
using Microsoft.VisualStudio.Workspace.Settings;
[Export(typeof(IWorkspaceSettingsProviderFactory))]
internal class MySettingsProviderFactory : IWorkspaceSettingsProviderFactory
{
public int Priority => 200; // Lower = higher priority; built-in is ~100
public IWorkspaceSettingsProvider CreateSettingsProvider(IWorkspace workspace)
{
return new MySettingsProvider(workspace);
}
}
Settings are aggregated across scopes (highest to lowest priority):
.vs/ folder ("local settings") at workspace rootTo auto-load your package when a folder is opened (instead of a solution), use the Open Folder UI context:
[ProvideAutoLoad(
"4646B819-1AE0-4E79-97F4-8A8176FDD664", // Open Folder UI context GUID
PackageAutoLoadFlags.BackgroundLoad)]
public sealed class MyExtensionPackage : ToolkitPackage
{
// ...
}
You can also implement IVsSolutionEvents7 for folder open/close events:
public void OnAfterOpenFolder(string folderPath)
{
// Folder was opened — initialize workspace-related features
}
public void OnAfterCloseFolder(string folderPath)
{
// Folder was closed — clean up
}
This usually means a MEF composition error. Check the error log at:
%LOCALAPPDATA%\Microsoft\VisualStudio\17.0_<id>\ComponentModelCache\Microsoft.VisualStudio.Default.err
Common causes:
IFileContextProvider but implementing IFileContextActionProvider)source.extension.vsixmanifestTextMate grammars work in Open Folder out of the box. If colorization isn't appearing, see the vs-textmate-grammar skill for registration steps.
Do NOT assume a solution is loaded — in Open Folder mode,
IVsSolutionreports no projects. Guard against null project references.
Do NOT use
ProvideAutoLoadwithSolutionExistsfor Open Folder features — use the Open Folder UI context GUID (4646B819-1AE0-4E79-97F4-8A8176FDD664).
Do NOT block the UI thread in file scanners or context providers — they run during indexing. Always use
async Taskimplementations.
npx claudepluginhub madskristensen/vs-agent-plugins --plugin vs-extensibility-skillsAdd custom tree nodes to Solution Explorer in Visual Studio extensions using the IAttachedCollectionSourceProvider MEF pattern. Works with VSSDK and VSIX Community Toolkit (in-process).
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.