Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ List<CompletionResult> ExecuteGetCommandCommand(bool useModulePrefix)
powershell.AddParameter("CommandType", types);
}

if (context.ExecutionContext.CompletionOptions.ExcludedModules?.Length > 0)
{
powershell.AddParameter("ExcludeModule", context.ExecutionContext.CompletionOptions.ExcludedModules);
}

// Exception is ignored, the user simply does not get any completion results if the pipeline fails
Exception exceptionThrown;
var commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);
Expand All @@ -188,6 +193,11 @@ List<CompletionResult> ExecuteGetCommandCommand(bool useModulePrefix)
powershell.AddParameter("CommandType", types);
}

if (context.ExecutionContext.CompletionOptions.ExcludedModules?.Length > 0)
{
powershell.AddParameter("ExcludeModule", context.ExecutionContext.CompletionOptions.ExcludedModules);
}

commandInfos = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown);
}

Expand Down Expand Up @@ -2471,6 +2481,16 @@ private static void NativeCommandArgumentCompletion(
break;
}

case "Set-CompletionOptions":
{
if (parameterName.EqualsOrdinalIgnoreCase("ExcludedModules"))
{
NativeCompletionModuleCommands(context, paramName: "Name", result);
}

break;
}

default:
{
NativeCompletionPathArgument(context, parameterName, result);
Expand Down Expand Up @@ -4626,7 +4646,46 @@ internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext
break;
}

return results.OrderBy(x => x.ToolTip);
switch (context.ExecutionContext.CompletionOptions.PathSorting)
{
case PathSorting.FullPath:
return results.OrderBy(x => x.ToolTip);

case PathSorting.ContainersFirst:
return results.OrderByDescending(x => x.ResultType).ThenBy(x => x.ListItemText);

default:
return results;
}
}

private static char GetPreferredSeparatorChar(ProviderInfo provider, string wordToComplete, PathSeparator userSetting, out bool isNonStandard)
{
char separator;
switch (userSetting)
{
case PathSeparator.LastUsed:
int index = wordToComplete.LastIndexOfAny(Utils.Separators.Directory);
separator = index == -1
? provider.ItemSeparator
: wordToComplete[index];
break;

case PathSeparator.Slash:
separator = '/';
break;

case PathSeparator.Backslash:
separator = '\\';
break;

default:
isNonStandard = false;
return provider.ItemSeparator;
}

isNonStandard = provider.ItemSeparator != separator;
return separator;
}

/// <summary>
Expand Down Expand Up @@ -4661,6 +4720,9 @@ private static List<CompletionResult> GetFileSystemProviderResults(
Diagnostics.Assert(provider.Name.Equals(FileSystemProvider.ProviderName), "Provider should be filesystem provider.");
#endif
var enumerationOptions = _enumerationOptions;
CompletionOptions completionOptions = context.ExecutionContext.CompletionOptions;
bool replaceSeparator;
char preferredSeparator = GetPreferredSeparatorChar(provider, context.WordToComplete, completionOptions.PreferredPathSeparator, out replaceSeparator);
var results = new List<CompletionResult>();
string homePath = inputUsedHome && !string.IsNullOrEmpty(provider.Home) ? provider.Home : null;

Expand Down Expand Up @@ -4702,6 +4764,10 @@ private static List<CompletionResult> GetFileSystemProviderResults(
basePath = basePath.EndsWith(provider.ItemSeparator)
? providerPrefix + basePath
: providerPrefix + basePath + provider.ItemSeparator;
if (replaceSeparator)
{
basePath = basePath.Replace(provider.ItemSeparator, preferredSeparator);
}
basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded);
}
else
Expand All @@ -4720,8 +4786,8 @@ private static List<CompletionResult> GetFileSystemProviderResults(
continue;
}

var entryName = entry.Name;
if (wildcardFilter is not null && !wildcardFilter.IsMatch(entryName))
var listItemText = entry.Name;
if (wildcardFilter is not null && !wildcardFilter.IsMatch(listItemText))
{
continue;
}
Expand All @@ -4737,22 +4803,28 @@ private static List<CompletionResult> GetFileSystemProviderResults(
}

basePath = basePath.Remove(basePath.Length - entry.Name.Length);
if (replaceSeparator)
{
basePath = basePath.Replace(provider.ItemSeparator, preferredSeparator);
}
basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded);
}

var resultType = isContainer
? CompletionResultType.ProviderContainer
: CompletionResultType.ProviderItem;

bool leafQuotesNeeded;

string leaf = isContainer && completionOptions.AddTrailingSeparatorForContainers
? listItemText + preferredSeparator
: listItemText;
var completionText = NewPathCompletionText(
basePath,
EscapePath(entryName, stringType, literalPaths, out leafQuotesNeeded),
EscapePath(leaf, stringType, literalPaths, out bool leafQuotesNeeded),
stringType,
containsNestedExpressions: false,
forceQuotes: baseQuotesNeeded || leafQuotesNeeded,
addAmpersand: false);
results.Add(new CompletionResult(completionText, entryName, resultType, entry.FullName));
results.Add(new CompletionResult(completionText, listItemText, resultType, entry.FullName));
}
}

Expand Down Expand Up @@ -4789,6 +4861,9 @@ private static List<CompletionResult> GetDefaultProviderResults(
? provider.Home
: null;

CompletionOptions completionOptions = context.ExecutionContext.CompletionOptions;
bool replaceSeparator;
char preferredSeparator = GetPreferredSeparatorChar(provider, context.WordToComplete, completionOptions.PreferredPathSeparator, out replaceSeparator);
var pattern = WildcardPattern.Get(filterText, WildcardOptions.IgnoreCase);
var results = new List<CompletionResult>();

Expand Down Expand Up @@ -4868,6 +4943,11 @@ private static List<CompletionResult> GetDefaultProviderResults(
basePath = basePath.Remove(basePath.Length - childNameList[0].Length);
}

if (replaceSeparator)
{
basePath = basePath.Replace(provider.ItemSeparator, preferredSeparator);
}

bool baseQuotesNeeded;
basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded);

Expand Down Expand Up @@ -4895,10 +4975,12 @@ private static List<CompletionResult> GetDefaultProviderResults(
resultType = CompletionResultType.Text;
}

bool leafQuotesNeeded;
string leaf = isContainer && completionOptions.AddTrailingSeparatorForContainers
? childName + preferredSeparator
: childName;
var completionText = NewPathCompletionText(
basePath,
EscapePath(childName, stringType, literalPaths, out leafQuotesNeeded),
EscapePath(leaf, stringType, literalPaths, out bool leafQuotesNeeded),
stringType,
containsNestedExpressions: false,
forceQuotes: baseQuotesNeeded || leafQuotesNeeded,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace System.Management.Automation
{
/// <summary>
/// Command for getting the completion options.
/// </summary>
[Cmdlet(VerbsCommon.Get, "CompletionOptions", HelpUri = "https://github.com/PowerShell/PowerShell")]
public sealed class GetCompletionOptionsCommand : PSCmdlet
{
/// <summary>
/// Writes the completion options to the pipeline.
/// </summary>
protected override void EndProcessing()
{
WriteObject(Context.CompletionOptions);
}
}

/// <summary>
/// Command for setting completion options.
/// </summary>
[Cmdlet(VerbsCommon.Set, "CompletionOptions", HelpUri = "https://github.com/PowerShell/PowerShell")]
public sealed class SetCompletionOptionsCommand : PSCmdlet
{
/// <summary>
/// Specifies if a separator character should automatically be added to container results.
/// </summary>
[Parameter()]
public bool AddTrailingSeparatorForContainers { get; set; }

/// <summary>
/// Specifies how path separators are treated in the completion results.
/// </summary>
[Parameter()]
public PathSeparator PreferredPathSeparator { get; set; }

/// <summary>
/// Specifies how paths should be sorted in the completion results.
/// </summary>
[Parameter()]
public PathSorting PathSorting { get; set; }

/// <summary>
/// Specifies modules that should not be included in command completion results.
/// </summary>
[Parameter()]
public string[] ExcludedModules { get; set; }

/// <summary>
/// Sets the user specified completion options.
/// </summary>
protected override void EndProcessing()
{
var completionOptions = Context.CompletionOptions;

foreach (var Key in MyInvocation.BoundParameters.Keys)
{
switch (Key)
{
case "AddTrailingSeparatorForContainers":
completionOptions.AddTrailingSeparatorForContainers = AddTrailingSeparatorForContainers;
break;

case "PreferredPathSeparator":
completionOptions.PreferredPathSeparator = PreferredPathSeparator;
break;

case "PathSorting":
completionOptions.PathSorting = PathSorting;
break;

case "ExcludedModules":
completionOptions.ExcludedModules = ExcludedModules;
break;

default:
break;
}
}
}
}

/// <summary>
/// Specifies settings used when PowerShell is generating completion results.
/// </summary>
public sealed class CompletionOptions
{
/// <summary>
/// Specifies if a separator character should automatically be added to container results.
/// </summary>
public bool AddTrailingSeparatorForContainers { get; internal set; }

/// <summary>
/// Specifies how path separators are treated in the completion results.
/// </summary>
public PathSeparator PreferredPathSeparator { get; internal set; }

/// <summary>
/// Specifies how paths should be sorted in the completion results.
/// </summary>
public PathSorting PathSorting { get; internal set; }

/// <summary>
/// Specifies modules that should not be included in command completion results.
/// </summary>
public string[] ExcludedModules { get; internal set; }

internal CompletionOptions()
{
ResetToDefault();
}

internal void ResetToDefault()
{
AddTrailingSeparatorForContainers = false;
PreferredPathSeparator = PathSeparator.Default;
PathSorting = PathSorting.FullPath;
ExcludedModules = null;
}
}

/// <summary>
/// Specifies how path separators are treated in the completion results.
/// </summary>
public enum PathSeparator
{
/// <summary>
/// Always use the default provider separator.
/// </summary>
Default = 0,

/// <summary>
/// Complete provider paths with the last separator in the input string.
/// If no separator has been used, the default provider separator is used.
/// If the provider doesn't support alternative separators, the supported separator will be used.
/// </summary>
LastUsed = 1,

/// <summary>
/// Always complete provider paths with "/", unless the provider explicitly don't support it.
/// </summary>
Slash = 2,

/// <summary>
/// Always complete provider paths with "\", unless the provider explicitly don't support it.
/// </summary>
Backslash = 3
}

/// <summary>
/// Specifies how paths should be sorted in the completion results.
/// </summary>
public enum PathSorting
{
/// <summary>
/// No sorting for file path completion results.
/// </summary>
None = 0,

/// <summary>
/// Sorts file path completion results alphabetically by the full path.
/// </summary>
FullPath = 1,

/// <summary>
/// Sorts file path completion results alphabetically by the full path, and lists containers first.
/// </summary>
ContainersFirst = 2
}
}
2 changes: 2 additions & 0 deletions src/System.Management.Automation/engine/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,8 @@ internal HelpSystem HelpSystem

internal Dictionary<string, ScriptBlock> NativeArgumentCompleters { get; set; }

internal CompletionOptions CompletionOptions { get; set; } = new CompletionOptions();

/// <summary>
/// Routine to create a command(processor) instance using the factory.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5497,6 +5497,8 @@ private static void InitializeCoreCmdletsAndProviders(
{ "Update-Help", new SessionStateCmdletEntry("Update-Help", typeof(UpdateHelpCommand), helpFile) },
{ "Wait-Job", new SessionStateCmdletEntry("Wait-Job", typeof(WaitJobCommand), helpFile) },
{ "Where-Object", new SessionStateCmdletEntry("Where-Object", typeof(WhereObjectCommand), helpFile) },
{ "Get-CompletionOptions", new SessionStateCmdletEntry("Get-CompletionOptions", typeof(GetCompletionOptionsCommand), helpFile) },
{ "Set-CompletionOptions", new SessionStateCmdletEntry("Set-CompletionOptions", typeof(SetCompletionOptionsCommand), helpFile) },
#if !CORECLR
{ "Add-PSSnapin", new SessionStateCmdletEntry("Add-PSSnapin", typeof(AddPSSnapinCommand), helpFile) },
{ "Export-Console", new SessionStateCmdletEntry("Export-Console", typeof(ExportConsoleCommand), helpFile) },
Expand Down
Loading