Skip to content

Commit

Permalink
Use the correct directory separator for tab completion based on the p…
Browse files Browse the repository at this point in the history
…latform we are working with (#3935)
  • Loading branch information
daxian-dbw authored Feb 1, 2024
1 parent d4ef931 commit 5fa2a21
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 7 deletions.
31 changes: 24 additions & 7 deletions PSReadLine/Completion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public partial class PSConsoleReadLine
private int _tabCommandCount;
private CommandCompletion _tabCompletions;
private Runspace _runspace;
private string _directorySeparator;

private static readonly Dictionary<CompletionResultType, PSKeyInfo []> KeysEndingCompletion =
new Dictionary<CompletionResultType, PSKeyInfo []>
Expand All @@ -40,7 +41,7 @@ public partial class PSConsoleReadLine
private static readonly char[] EolChars = {'\r', '\n'};

// String helper for directory paths
private static readonly string DirectorySeparatorString = System.IO.Path.DirectorySeparatorChar.ToString();
private static readonly string DefaultDirectorySeparator = System.IO.Path.DirectorySeparatorChar.ToString();

// Stub helper method so completion can be mocked
[ExcludeFromCodeCoverage]
Expand Down Expand Up @@ -281,10 +282,26 @@ private CommandCompletion GetCompletions()
System.Management.Automation.PowerShell ps;
if (!_mockableMethods.RunspaceIsRemote(_runspace))
{
_directorySeparator ??= DefaultDirectorySeparator;
ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
}
else
{
if (_directorySeparator is null)
{
// Use the default separator by default.
_directorySeparator = DefaultDirectorySeparator;
PSPrimitiveDictionary dict = _runspace.GetApplicationPrivateData();

if (dict["PSVersionTable"] is PSPrimitiveDictionary versionTable)
{
// If the 'Platform' key is available and its value is not 'Win*', then the server side is macOS or Linux.
// In that case, we use the forward slash '/' as the directory separator.
// Otherwise, the server side is Windows and we use the backward slash '\' instead.
_directorySeparator = versionTable["Platform"] is string platform && !platform.StartsWith("Win", StringComparison.Ordinal) ? "/" : @"\";
}
}

ps = System.Management.Automation.PowerShell.Create();
ps.Runspace = _runspace;
}
Expand Down Expand Up @@ -375,25 +392,25 @@ private void DoReplacementForCompletion(CompletionResult completionResult, Comma
completions.ReplacementLength = replacementText.Length;
}

private static string GetReplacementTextForDirectory(string replacementText, ref int cursorAdjustment)
private string GetReplacementTextForDirectory(string replacementText, ref int cursorAdjustment)
{
if (!replacementText.EndsWith(DirectorySeparatorString , StringComparison.Ordinal))
if (!replacementText.EndsWith(_directorySeparator , StringComparison.Ordinal))
{
if (replacementText.EndsWith(String.Format("{0}\'", DirectorySeparatorString), StringComparison.Ordinal) ||
replacementText.EndsWith(String.Format("{0}\"", DirectorySeparatorString), StringComparison.Ordinal))
if (replacementText.EndsWith(string.Format("{0}\'", _directorySeparator), StringComparison.Ordinal) ||
replacementText.EndsWith(string.Format("{0}\"", _directorySeparator), StringComparison.Ordinal))
{
cursorAdjustment = -1;
}
else if (replacementText.EndsWith("'", StringComparison.Ordinal) ||
replacementText.EndsWith("\"", StringComparison.Ordinal))
{
var len = replacementText.Length;
replacementText = replacementText.Substring(0, len - 1) + System.IO.Path.DirectorySeparatorChar + replacementText[len - 1];
replacementText = replacementText.Substring(0, len - 1) + _directorySeparator + replacementText[len - 1];
cursorAdjustment = -1;
}
else
{
replacementText = replacementText + System.IO.Path.DirectorySeparatorChar;
replacementText += _directorySeparator;
}
}
return replacementText;
Expand Down
7 changes: 7 additions & 0 deletions PSReadLine/ReadLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,13 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics)
_engineIntrinsics = engineIntrinsics;
_runspace = runspace;

// The directory separator to be used for tab completion may change depending on
// whether we are working with a remote Runspace.
// So, we always set it to null for every call into 'PSConsoleReadLine.ReadLine',
// and do the real initialization when tab completion is triggered for the first
// time during that call.
_directorySeparator = null;

// Update the client instance per every call to PSReadLine.
UpdatePredictionClient(runspace, engineIntrinsics);

Expand Down

0 comments on commit 5fa2a21

Please sign in to comment.