Skip to content

Commit 5fa2a21

Browse files
authored
Use the correct directory separator for tab completion based on the platform we are working with (#3935)
1 parent d4ef931 commit 5fa2a21

File tree

2 files changed

+31
-7
lines changed

2 files changed

+31
-7
lines changed

PSReadLine/Completion.cs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public partial class PSConsoleReadLine
2323
private int _tabCommandCount;
2424
private CommandCompletion _tabCompletions;
2525
private Runspace _runspace;
26+
private string _directorySeparator;
2627

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

4243
// String helper for directory paths
43-
private static readonly string DirectorySeparatorString = System.IO.Path.DirectorySeparatorChar.ToString();
44+
private static readonly string DefaultDirectorySeparator = System.IO.Path.DirectorySeparatorChar.ToString();
4445

4546
// Stub helper method so completion can be mocked
4647
[ExcludeFromCodeCoverage]
@@ -281,10 +282,26 @@ private CommandCompletion GetCompletions()
281282
System.Management.Automation.PowerShell ps;
282283
if (!_mockableMethods.RunspaceIsRemote(_runspace))
283284
{
285+
_directorySeparator ??= DefaultDirectorySeparator;
284286
ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace);
285287
}
286288
else
287289
{
290+
if (_directorySeparator is null)
291+
{
292+
// Use the default separator by default.
293+
_directorySeparator = DefaultDirectorySeparator;
294+
PSPrimitiveDictionary dict = _runspace.GetApplicationPrivateData();
295+
296+
if (dict["PSVersionTable"] is PSPrimitiveDictionary versionTable)
297+
{
298+
// If the 'Platform' key is available and its value is not 'Win*', then the server side is macOS or Linux.
299+
// In that case, we use the forward slash '/' as the directory separator.
300+
// Otherwise, the server side is Windows and we use the backward slash '\' instead.
301+
_directorySeparator = versionTable["Platform"] is string platform && !platform.StartsWith("Win", StringComparison.Ordinal) ? "/" : @"\";
302+
}
303+
}
304+
288305
ps = System.Management.Automation.PowerShell.Create();
289306
ps.Runspace = _runspace;
290307
}
@@ -375,25 +392,25 @@ private void DoReplacementForCompletion(CompletionResult completionResult, Comma
375392
completions.ReplacementLength = replacementText.Length;
376393
}
377394

378-
private static string GetReplacementTextForDirectory(string replacementText, ref int cursorAdjustment)
395+
private string GetReplacementTextForDirectory(string replacementText, ref int cursorAdjustment)
379396
{
380-
if (!replacementText.EndsWith(DirectorySeparatorString , StringComparison.Ordinal))
397+
if (!replacementText.EndsWith(_directorySeparator , StringComparison.Ordinal))
381398
{
382-
if (replacementText.EndsWith(String.Format("{0}\'", DirectorySeparatorString), StringComparison.Ordinal) ||
383-
replacementText.EndsWith(String.Format("{0}\"", DirectorySeparatorString), StringComparison.Ordinal))
399+
if (replacementText.EndsWith(string.Format("{0}\'", _directorySeparator), StringComparison.Ordinal) ||
400+
replacementText.EndsWith(string.Format("{0}\"", _directorySeparator), StringComparison.Ordinal))
384401
{
385402
cursorAdjustment = -1;
386403
}
387404
else if (replacementText.EndsWith("'", StringComparison.Ordinal) ||
388405
replacementText.EndsWith("\"", StringComparison.Ordinal))
389406
{
390407
var len = replacementText.Length;
391-
replacementText = replacementText.Substring(0, len - 1) + System.IO.Path.DirectorySeparatorChar + replacementText[len - 1];
408+
replacementText = replacementText.Substring(0, len - 1) + _directorySeparator + replacementText[len - 1];
392409
cursorAdjustment = -1;
393410
}
394411
else
395412
{
396-
replacementText = replacementText + System.IO.Path.DirectorySeparatorChar;
413+
replacementText += _directorySeparator;
397414
}
398415
}
399416
return replacementText;

PSReadLine/ReadLine.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,13 @@ private void Initialize(Runspace runspace, EngineIntrinsics engineIntrinsics)
693693
_engineIntrinsics = engineIntrinsics;
694694
_runspace = runspace;
695695

696+
// The directory separator to be used for tab completion may change depending on
697+
// whether we are working with a remote Runspace.
698+
// So, we always set it to null for every call into 'PSConsoleReadLine.ReadLine',
699+
// and do the real initialization when tab completion is triggered for the first
700+
// time during that call.
701+
_directorySeparator = null;
702+
696703
// Update the client instance per every call to PSReadLine.
697704
UpdatePredictionClient(runspace, engineIntrinsics);
698705

0 commit comments

Comments
 (0)