diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index c20057c397c..595e069a1fc 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -4174,7 +4174,7 @@ internal static IEnumerable CompleteFilename(CompletionContext string[] entries = null; try { - entries = Directory.GetFileSystemEntries(parentPath, leaf); + entries = Directory.GetFileSystemEntries(parentPath, leaf, _enumerationOptions); } catch (Exception) { @@ -4404,6 +4404,11 @@ private struct SHARE_INFO_1 private const int ERROR_MORE_DATA = 234; private const int STYPE_DISKTREE = 0; private const int STYPE_MASK = 0x000000FF; + private static System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions + { + MatchCasing = MatchCasing.CaseInsensitive, + AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior + }; [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] private static extern int NetShareEnum(string serverName, int level, out IntPtr bufptr, int prefMaxLen, diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 141b7803c97..b7430483890 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -81,6 +81,11 @@ public FileSystemProvider() } private Collection _excludeMatcher = null; + private static System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions + { + MatchCasing = MatchCasing.CaseInsensitive, + AttributesToSkip = 0 // Default is to skip Hidden and System files, so we clear this to retain existing behavior + }; /// /// Converts all / in the path to \ @@ -1569,7 +1574,7 @@ private void Dir( else { // Filter the directories - target.Add(directory.EnumerateDirectories(Filter)); + target.Add(directory.EnumerateDirectories(Filter, _enumerationOptions)); } // Making sure to obey the StopProcessing. @@ -1580,7 +1585,7 @@ private void Dir( // Use the specified filter when retrieving the // children - target.Add(directory.EnumerateFiles(Filter)); + target.Add(directory.EnumerateFiles(Filter, _enumerationOptions)); } else { diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index cdf07689f80..c541b9fce1d 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -222,6 +222,7 @@ Describe "TabCompletion" -Tags CI { $oneSubDir = Join-Path -Path $tempDir -ChildPath "oneSubDir" $oneSubDirPrime = Join-Path -Path $tempDir -ChildPath "prime" $twoSubDir = Join-Path -Path $oneSubDir -ChildPath "twoSubDir" + $caseTestPath = Join-Path $testdrive "CaseTest" New-Item -Path $tempDir -ItemType Directory -Force > $null New-Item -Path $oneSubDir -ItemType Directory -Force > $null @@ -254,12 +255,17 @@ Describe "TabCompletion" -Tags CI { } } + BeforeEach { + New-Item -ItemType Directory -Path $caseTestPath > $null + } + AfterAll { Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue } AfterEach { Pop-Location + Remove-Item -Path $caseTestPath -Recurse -Force -ErrorAction SilentlyContinue } It "Input '' should successfully complete" -TestCases $testCases { @@ -318,6 +324,48 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches | Should -HaveCount 1 $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab } + + It "Test case insensitive path" -Skip:(!$IsLinux) -TestCases @( + @{ type = "File" ; beforeTab = "Get-Content f" }, + @{ type = "Directory"; beforeTab = "cd f" } + ) { + param ($type, $beforeTab) + + $testItems = "foo", "Foo", "fOO" + $testItems | ForEach-Object { + $itemPath = Join-Path $caseTestPath $_ + New-Item -ItemType $type -Path $itemPath + } + Push-Location $caseTestPath + $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length + $res.CompletionMatches | Should -HaveCount $testItems.Count + + # order isn't guaranteed so we'll sort them first + $completions = ($res.CompletionMatches | Sort-Object CompletionText -CaseSensitive).CompletionText -join ":" + $expected = ($testItems | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" + + $completions | Should -BeExactly $expected + } + + It "Test case insensitive file and folder path completing for " -Skip:(!$IsLinux) -TestCases @( + @{ type = "File" ; beforeTab = "Get-Content f"; expected = "foo","Foo" }, # Get-Content passes thru to provider + @{ type = "Directory"; beforeTab = "cd f" ; expected = "Foo" } # Set-Location is aware of Files vs Folders + ) { + param ($beforeTab, $expected) + + $filePath = Join-Path $caseTestPath "foo" + $folderPath = Join-Path $caseTestPath "Foo" + New-Item -ItemType File -Path $filePath + New-Item -ItemType Directory -Path $folderPath + Push-Location $caseTestPath + $res = TabExpansion2 -inputScript $beforeTab -cursorColumn $beforeTab.Length + $res.CompletionMatches | Should -HaveCount $expected.Count + + # order isn't guaranteed so we'll sort them first + $completions = ($res.CompletionMatches | Sort-Object CompletionText -CaseSensitive).CompletionText -join ":" + $expected = ($expected | Sort-Object -CaseSensitive | ForEach-Object { "./$_" }) -join ":" + + } } Context "Cmdlet name completion" {