diff --git a/src/CompletionPredictor.cs b/src/CompletionPredictor.cs index a2920b5..4f5f7c2 100644 --- a/src/CompletionPredictor.cs +++ b/src/CompletionPredictor.cs @@ -19,7 +19,8 @@ public partial class CompletionPredictor : ICommandPredictor, IDisposable "ForEach-Object", "?", "where", - "Where-Object" + "Where-Object", + "cd", }; internal CompletionPredictor(string guid) @@ -62,33 +63,59 @@ public SuggestionPackage GetSuggestion(PredictionClient client, PredictionContex return default; } + return GetFromTabCompletion(context, cancellationToken); + } + + private SuggestionPackage GetFromTabCompletion(PredictionContext context, CancellationToken cancellationToken) + { // Call into PowerShell tab completion to get completion results. // The runspace may be held by another call, or the call may take too long and exceed the timeout. CommandCompletion? result = GetCompletionResults(context.InputAst, context.InputTokens, context.CursorPosition); - if (result is null || cancellationToken.IsCancellationRequested) + if (result is null || result.CompletionMatches.Count == 0 || cancellationToken.IsCancellationRequested) { return default; } - int count = result.CompletionMatches.Count; - if (count > 0) - { - count = count > 10 ? 10 : count; - var list = new List(count); + int count = result.CompletionMatches.Count > 30 ? 30 : result.CompletionMatches.Count; + List? list = null; + + int replaceIndex = result.ReplacementIndex; + string input = context.InputAst.Extent.Text; - string input = context.InputAst.Extent.Text; - var head = result.ReplacementIndex == 0 ? ReadOnlySpan.Empty : input.AsSpan(0, result.ReplacementIndex); + ReadOnlySpan head = replaceIndex == 0 ? ReadOnlySpan.Empty : input.AsSpan(0, replaceIndex); + ReadOnlySpan diff = input.AsSpan(replaceIndex); - for (int i = 0; i < count; i++) + for (int i = 0; i < count; i++) + { + CompletionResult completion = result.CompletionMatches[i]; + ReadOnlySpan text = completion.CompletionText.AsSpan(); + string? suggestion = null; + + switch (completion.ResultType) { - var completion = result.CompletionMatches[i]; - list.Add(new PredictiveSuggestion(string.Concat(head, completion.CompletionText), completion.ToolTip)); + case CompletionResultType.ProviderItem or CompletionResultType.ProviderContainer when !diff.IsEmpty: + // For local paths, if the input doesn't contain the prefix, then we stripe it from the suggestion. + bool removeLocalPathPrefix = + text.IndexOf(diff, StringComparison.OrdinalIgnoreCase) == 2 && + text[0] == '.' && text[1] == Path.DirectorySeparatorChar; + ReadOnlySpan newPart = removeLocalPathPrefix ? text.Slice(2) : text; + suggestion = string.Concat(head, newPart); + + break; + + default: + break; } - return new SuggestionPackage(list); + suggestion ??= string.Concat(head, text); + if (!string.Equals(input, suggestion, StringComparison.OrdinalIgnoreCase)) + { + list ??= new List(count); + list.Add(new PredictiveSuggestion(suggestion, completion.ToolTip)); + } } - return default; + return list is null ? default : new SuggestionPackage(list); } private CommandCompletion? GetCompletionResults(Ast inputAst, IReadOnlyCollection inputTokens, IScriptPosition cursorPosition)