Guides adding custom IntelliSense completion items to Visual Studio via MEF-based VSSDK/VSIX extensions. Covers completion sources, providers, content types, and MEF asset declarations.
How this skill is triggered — by the user, by Claude, or both
Slash command
/vs-extensibility-skills:adding-intellisense-completionThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Completion sources provide IntelliSense auto-complete suggestions. Common scenarios:
Completion sources provide IntelliSense auto-complete suggestions. Common scenarios:
IntelliSense is one of the most visible and frequently used editor features — developers trigger it dozens of times per session. A poorly performing completion source is immediately noticeable.
When to use completion vs. alternatives:
.vsixmanifestIAsyncCompletionSource)IAsyncCompletionSourceProvider)Any extension that uses MEF editor exports must declare the MEF asset type in the .vsixmanifest file. Without this, Visual Studio will not discover your MEF components and your completion source will not load.
Add this inside the <Assets> element of source.extension.vsixmanifest:
<Asset Type="Microsoft.VisualStudio.MefComponent"
d:Source="Project"
d:ProjectName="%CurrentProject%"
Path="|%CurrentProject%|" />
The VisualStudio.Extensibility SDK does not currently support custom IntelliSense completion sources. Completion is provided by in-process MEF components.
If you need custom completion, use the VSSDK (in-process) MEF approach.
The Community Toolkit does not add a separate completion API — completion uses the same MEF-based VSSDK APIs described below. The toolkit can simplify package setup, but the completion components use standard MEF exports.
Visual Studio has two completion APIs:
IAsyncCompletionSourceProvider) — Visual Studio 16.0+, preferredICompletionSourceProvider) — older API, still worksNuGet packages: Microsoft.VisualStudio.SDK, Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
Key namespace: Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Core.Imaging;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
using Microsoft.VisualStudio.Language.StandardClassification;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Text.Editor;
namespace MyExtension;
internal sealed class MyCompletionSource : IAsyncCompletionSource
{
private static readonly ImageElement Icon = new(
KnownMonikers.Keyword.ToImageId(), "Keyword");
public Task<CompletionContext> GetCompletionContextAsync(
IAsyncCompletionSession session,
CompletionTrigger trigger,
SnapshotPoint triggerLocation,
SnapshotSpan applicableToSpan,
CancellationToken cancellationToken)
{
var items = ImmutableArray.CreateBuilder<CompletionItem>();
// Add your completion items
items.Add(new CompletionItem("MyKeyword1", this, Icon));
items.Add(new CompletionItem("MyKeyword2", this, Icon));
items.Add(new CompletionItem("MyFunction", this, Icon));
return Task.FromResult(new CompletionContext(items.ToImmutable()));
}
public Task<object> GetDescriptionAsync(
IAsyncCompletionSession session,
CompletionItem item,
CancellationToken cancellationToken)
{
// Return a tooltip description for the selected item
return Task.FromResult<object>($"Description for {item.DisplayText}");
}
public CompletionStartData InitializeCompletion(
CompletionTrigger trigger,
SnapshotPoint triggerLocation,
CancellationToken cancellationToken)
{
// Determine if completion should start and what span it applies to
if (trigger.Reason == CompletionTriggerReason.Insertion
&& !char.IsLetterOrDigit(trigger.Character))
{
return CompletionStartData.DoesNotParticipateInCompletion;
}
// Find the start of the current word
var line = triggerLocation.GetContainingLine();
int start = triggerLocation.Position;
while (start > line.Start.Position
&& char.IsLetterOrDigit((triggerLocation.Snapshot[start - 1])))
{
start--;
}
var applicableSpan = new SnapshotSpan(
triggerLocation.Snapshot, start, triggerLocation.Position - start);
return new CompletionStartData(
CompletionParticipation.ProvidesItems, applicableSpan);
}
}
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
namespace MyExtension;
[Export(typeof(IAsyncCompletionSourceProvider))]
[ContentType("text")] // or your content type
[Name("MyCompletionSourceProvider")]
internal sealed class MyCompletionSourceProvider : IAsyncCompletionSourceProvider
{
public IAsyncCompletionSource GetOrCreate(ITextView textView)
{
return textView.Properties.GetOrCreateSingletonProperty(
() => new MyCompletionSource());
}
}
NuGet packages: Microsoft.VisualStudio.SDK, Microsoft.VisualStudio.Language.Intellisense
Key namespace: Microsoft.VisualStudio.Language.Intellisense
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Operations;
using Microsoft.VisualStudio.Utilities;
namespace MyExtension;
[Export(typeof(ICompletionSourceProvider))]
[ContentType("text")]
[Name("MyLegacyCompletionProvider")]
internal sealed class MyLegacyCompletionSourceProvider : ICompletionSourceProvider
{
[Import]
internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
public ICompletionSource TryCreateCompletionSource(ITextBuffer textBuffer)
{
return new MyLegacyCompletionSource(textBuffer, NavigatorService);
}
}
internal sealed class MyLegacyCompletionSource : ICompletionSource
{
private readonly ITextBuffer _buffer;
private readonly ITextStructureNavigatorSelectorService _navigatorService;
private bool _disposed;
public MyLegacyCompletionSource(
ITextBuffer buffer,
ITextStructureNavigatorSelectorService navigatorService)
{
_buffer = buffer;
_navigatorService = navigatorService;
}
public void AugmentCompletionSession(
ICompletionSession session,
IList<CompletionSet> completionSets)
{
var completions = new List<Completion>
{
new Completion("MyKeyword1", "MyKeyword1", "Description 1", null, null),
new Completion("MyKeyword2", "MyKeyword2", "Description 2", null, null),
new Completion("MyFunction", "MyFunction()", "Description 3", null, null),
};
var navigator = _navigatorService.GetTextStructureNavigator(_buffer);
var extent = navigator.GetExtentOfWord(
session.GetTriggerPoint(_buffer).GetPoint(_buffer.CurrentSnapshot));
var trackingSpan = _buffer.CurrentSnapshot.CreateTrackingSpan(
extent.Span, SpanTrackingMode.EdgeInclusive);
completionSets.Add(new CompletionSet(
"MyCompletions",
"My Completions",
trackingSpan,
completions,
null));
}
public void Dispose()
{
if (!_disposed)
{
GC.SuppressFinalize(this);
_disposed = true;
}
}
}
If you need to programmatically trigger completion (e.g., after typing a .), register a command handler:
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Utilities;
[Export(typeof(ICommandHandler))]
[ContentType("text")]
[Name("MyCompletionTriggerHandler")]
internal sealed class CompletionTriggerHandler : ICommandHandler<TypeCharCommandArgs>
{
[Import]
internal IAsyncCompletionBroker CompletionBroker { get; set; }
public string DisplayName => "My Completion Trigger";
public bool ExecuteCommand(TypeCharCommandArgs args, CommandExecutionContext executionContext)
{
if (args.TypedChar == '.')
{
// Trigger completion after '.'
var trigger = new CompletionTrigger(
CompletionTriggerReason.Insertion, args.TextView.TextSnapshot, '.');
CompletionBroker.TriggerCompletion(
args.TextView, trigger, args.TextView.Caret.Position.BufferPosition, default);
}
return false; // Let the character be typed
}
public CommandState GetCommandState(TypeCharCommandArgs args)
{
return CommandState.Unspecified;
}
}
IAsyncCompletionSourceProvider) for new extensions.InitializeCompletion determines whether your source participates and defines the applicable span.GetCompletionContextAsync returns the actual items — it runs on a background thread, so it's safe to do I/O.ICompletionSource.AugmentCompletionSession runs synchronously.[ContentType] to scope completion to specific file types.KnownMonikers from Microsoft.VisualStudio.Imaging..vsixmanifest — see the top of this document.IAsyncCompletionSourceProvider (modern) or ICompletionSourceProvider (legacy) via MEF exports.source.extension.vsixmanifest.[ContentType] to avoid interfering with other languages..vsixmanifest. Also verify [ContentType] matches the file type. For async completion, ensure InitializeCompletion returns a valid CompletionStartData (not DoesNotParticipateInCompletion).[ContentType] attribute on the provider. Without it, completion activates for all file types.ApplicableToSpan calculation. The span determines which text VS uses to filter the completion list as the user types.ICompletionSource or doing I/O in GetCompletionContextAsync. Move heavy work to background caching or use the async API.CompletionItem.SortText and FilterText to control ordering and filtering.Do NOT use the legacy synchronous
ICompletionSourceProvider/ICompletionSourceAPI for VS 2019+ — it runs on the UI thread and freezes the editor on I/O. UseIAsyncCompletionSourceProvider/IAsyncCompletionSource. Old tutorials still show the legacy API; do not follow them.
Do NOT do expensive work in
GetCompletionContextAsync— it's called on every keystroke. Pre-compute or cache data inInitializeCompletionor via background threads.
Do NOT forget to return
CompletionStartData.DoesNotParticipateInCompletionfromInitializeCompletionwhen your source shouldn't contribute — returning items adds overhead and pollutes the list.
Do NOT forget the
MefComponentasset type in.vsixmanifest— without it, your provider is silently ignored (#1 cause of "my completion doesn't load").
Do NOT omit the
[ContentType]attribute — without it, your completion source may activate for every file type in VS.
npx claudepluginhub madskristensen/vs-agent-plugins --plugin vs-extensibility-skillsExtends the Visual Studio editor lightbulb with custom code fixes and refactorings via the MEF-based ISuggestedAction API. Covers legacy, category-based, and async streaming APIs for non-Roslyn languages.
Guides VS Code extension installation in Cursor IDE using Open VSX, VSIX files, CLI commands; resolves AI conflicts and recommends essentials.
Provides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.