Where PowerShell Command Completion Code Lives: Inside the TabExpansion2 Engine
PowerShell's command completion logic resides in the src/System.Management.Automation/engine/CommandCompletion/ directory, where the TabExpansion2 function serves as the entry point that delegates to the CommandCompletion class and CompletionCompleters for generating suggestions.
The PowerShell command completion system powers the interactive tab-completion experience you use every time you press Tab in the terminal. Unlike monolithic shell implementations, PowerShell separates its completion engine into a dedicated subsystem within the open-source PowerShell/PowerShell repository, exposing both internal APIs and extensibility points for custom completers.
The Command Completion Pipeline Architecture
The completion flow follows a strict pipeline from user input to suggestion display:
- User presses Tab → The host invokes the built-in function
TabExpansion2 CommandCompletion.CompleteInputcreates a completion context and initiates analysisCompletionAnalysisparses the input into AST nodes and tokens, building aCompletionContextCompletionCompletersdispatches to specialized completers based on token type (commands, parameters, members, variables)CompletionHelpershandles quote normalization and wildcard matchingCompletionResultobjects return the final suggestions to the UI
Entry Point: The TabExpansion2 Function
The TabExpansion2 function is defined as a hidden script function in src/System.Management.Automation/engine/InitialSessionState.cs. During engine startup, the following script is registered via SessionStateFunctionEntry.GetDelayParsedFunctionEntry("TabExpansion2", …):
private const string s_tabExpansionFunctionText = @"
param($commandName, $wordToComplete, $cursorPosition, $options)
# Calls the CommandCompletion engine
[System.Management.Automation.CommandCompletion]::CompleteInput($null,$commandName,$wordToComplete,$cursorPosition,$options,$null,$null)
";
This function acts as the bridge between the host application (PowerShell console, VS Code, etc.) and the .NET completion engine.
Core Engine: CommandCompletion and CompletionAnalysis
CommandCompletion.cs
The primary API surface lives in src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs. The static CompleteInput method serves as the external entry point:
public static List<CompletionResult> CompleteInput(
PowerShell powershell,
string input,
int cursor,
Hashtable options,
out int replacementIndex,
out int replacementLength)
{
// …parse the input into AST & tokens…
var analysis = new CompletionAnalysis(ast, tokens, cursorPos, options);
return analysis.GetResults(powershell, out replacementIndex, out replacementLength);
}
CompletionAnalysis.cs
The CompletionAnalysis class in src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs inspects the AST (Abstract Syntax Tree) to determine the completion context. The ExtractAstContext method identifies the token under the cursor and creates a CompletionContext object containing:
- The AST node at the cursor position
- Token stream from the parser
- Word to complete (partial string)
- Execution context for invoking sub-commands
Concrete Completers: CompletionCompleters
All domain-specific completion logic lives in src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs. This file contains the dispatch logic that routes to specialized completers based on token kind:
switch (tokenAtCursor.Kind)
{
case TokenKind.Identifier:
result = GetResultForIdentifier(completionContext, ref replacementIndex, ref replacementLength);
break;
case TokenKind.Parameter:
result = CompletionCompleters.CompleteCommandParameter(completionContext);
break;
case TokenKind.Dot:
case TokenKind.ColonColon:
result = CompletionCompleters.CompleteMember(completionContext,
@static: tokenAtCursor.Kind == TokenKind.ColonColon,
ref replacementLength);
break;
// …many other cases (variables, strings, operators, etc.)…
}
The CompletionCompleters class implements static methods like CompleteCommandParameter, CompleteMember, and CompleteVariable, each handling a specific completion scenario.
Helper Utilities and Result Types
CompletionHelpers.cs
Utility functions in src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs handle cross-cutting concerns:
HandleDoubleAndSingleQuoteandQuoteCompletionTextmanage quote insertion and escapingLiteralMatchOrdinalIgnoreCaseandWildcardPatternMatchIgnoreCaseprovide matching logic for filename completion- Helper methods create properly formatted
CompletionResultinstances
CompletionResult.cs
The CompletionResult class defined in src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs encapsulates the final suggestion data:
CompletionText: The actual text inserted into the command lineListItemText: The display text shown in dropdown menusToolTip: Descriptive text (parameter help, type info)ResultType: Enum indicating Command, Parameter, Property, Method, etc.
Programmatic Access Examples
Simulating Tab Completion from PowerShell
You can invoke the completion engine directly without pressing Tab:
# Emulate pressing Tab after typing "Get-Ser"
$input = 'Get-Ser'
$cursor = $input.Length # cursor at end of line
$repIdx = 0
$repLen = 0
[System.Management.Automation.CommandCompletion]::CompleteInput(
$null, $input, $cursor, @{}, [ref]$repIdx, [ref]$repLen)
This returns a collection of CompletionResult objects containing suggestions like Get-Service.
Registering a Custom Argument Completer
Custom completers integrate with the engine via Register-ArgumentCompleter:
# Register a script block that provides completions for a fake command
Register-ArgumentCompleter -CommandName 'Invoke-MyCmd' -ParameterName 'Path' -ScriptBlock {
param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter)
# Return any .txt files in the current directory that start with the word typed
Get-ChildItem -Path . -Filter "$wordToComplete*.txt" |
ForEach-Object {
[System.Management.Automation.CompletionResult]::new(
$_.Name, $_.Name, 'ParameterValue', $_.FullName)
}
}
The engine stores these script blocks in the CustomArgumentCompleters dictionary within CompletionContext and invokes them when completing the specified command parameters.
Accessing Completion from C#
For tools and editors embedding PowerShell, access completions via the public API:
using System.Management.Automation;
using System.Management.Automation.Language;
// Build a PowerShell instance
PowerShell ps = PowerShell.Create(RunspaceMode.CurrentRunspace);
// Request completions for "Get-P*" at cursor position 7
var results = CommandCompletion.CompleteInput(
ps,
"Get-P*",
7,
new Hashtable(),
out int replIdx,
out int replLen);
foreach (var r in results)
{
Console.WriteLine($"{r.CompletionText} // {r.ToolTip}");
}
Summary
- Entry Point: The
TabExpansion2function inInitialSessionState.csforwards Tab key presses to the completion engine. - Core Logic:
CommandCompletion.CompleteInputinCommandCompletion.csorchestrates the analysis and result generation. - Context Building:
CompletionAnalysisparses the command line AST to determine what type of completion is needed. - Specialized Completers:
CompletionCompleterscontains the concrete implementations for commands, parameters, members, and variables. - Utilities:
CompletionHelpershandles quote processing and wildcard matching, whileCompletionResultdefines the data contract for suggestions. - Extensibility: The
Register-ArgumentCompletercommand stores custom completers inCompletionContext.CustomArgumentCompletersfor invocation during completion.
Frequently Asked Questions
What is the difference between TabExpansion and TabExpansion2?
TabExpansion2 is the modern completion entry point that replaced the legacy TabExpansion function. According to the PowerShell source code in InitialSessionState.cs, TabExpansion2 calls the managed [System.Management.Automation.CommandCompletion]::CompleteInput API, whereas the older TabExpansion relied primarily on script-based completion logic. All modern PowerShell hosts use TabExpansion2 to leverage the full AST-based completion engine.
How do I create a custom argument completer in PowerShell?
Use the Register-ArgumentCompleter cmdlet to bind a script block to specific commands or parameters. Your script block receives parameters including $wordToComplete, $commandAst, and $fakeBoundParameter, and must return CompletionResult objects. The engine caches these completers in the CustomArgumentCompleters dictionary and invokes them automatically when the user requests completion for registered commands.
Where are the built-in completers for commands and parameters defined?
Built-in completers live in the CompletionCompleters class within src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs. This file contains static methods like CompleteCommandParameter for parameter names, CompleteMember for properties and methods, and specialized handlers for variables and filenames. The class uses a switch statement on TokenKind to dispatch to the appropriate completer based on the cursor context.
Can I invoke PowerShell's completion engine from C# without running a script?
Yes, by calling CommandCompletion.CompleteInput statically from the System.Management.Automation namespace. Pass a PowerShell instance, the input string, cursor position, and optional completion options. The method returns a List<CompletionResult> containing the suggestions and outputs the replacementIndex and replacementLength to indicate which portion of the input should be replaced.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →