| File: Completion\FileSystemCompletionHelper.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion; internal class FileSystemCompletionHelper { private static readonly char[] s_windowsDirectorySeparator = ['\\']; private readonly Glyph _folderGlyph; private readonly Glyph _fileGlyph; // absolute paths private readonly ImmutableArray<string> _searchPaths; private readonly string? _baseDirectory; private readonly ImmutableArray<string> _allowableExtensions; private readonly CompletionItemRules _itemRules; public FileSystemCompletionHelper( Glyph folderGlyph, Glyph fileGlyph, ImmutableArray<string> searchPaths, string? baseDirectory, ImmutableArray<string> allowableExtensions, CompletionItemRules itemRules) { Debug.Assert(searchPaths.All(PathUtilities.IsAbsolute)); Debug.Assert(baseDirectory == null || PathUtilities.IsAbsolute(baseDirectory)); _searchPaths = searchPaths; _baseDirectory = baseDirectory; _allowableExtensions = allowableExtensions; _folderGlyph = folderGlyph; _fileGlyph = fileGlyph; _itemRules = itemRules; } // virtual for testing protected virtual string[] GetLogicalDrives() => IOUtilities.PerformIO(Directory.GetLogicalDrives, []); // virtual for testing protected virtual bool DirectoryExists(string fullPath) { Debug.Assert(PathUtilities.IsAbsolute(fullPath)); return Directory.Exists(fullPath); } // virtual for testing protected virtual IEnumerable<string> EnumerateDirectories(string fullDirectoryPath) { Debug.Assert(PathUtilities.IsAbsolute(fullDirectoryPath)); return IOUtilities.PerformIO(() => Directory.EnumerateDirectories(fullDirectoryPath), []); } // virtual for testing protected virtual IEnumerable<string> EnumerateFiles(string fullDirectoryPath) { Debug.Assert(PathUtilities.IsAbsolute(fullDirectoryPath)); return IOUtilities.PerformIO(() => Directory.EnumerateFiles(fullDirectoryPath), []); } // virtual for testing protected virtual bool IsVisibleFileSystemEntry(string fullPath) { Debug.Assert(PathUtilities.IsAbsolute(fullPath)); return IOUtilities.PerformIO(() => (File.GetAttributes(fullPath) & (FileAttributes.Hidden | FileAttributes.System)) == 0, false); } private CompletionItem CreateNetworkRoot() => CommonCompletionItem.Create( "\\\\", displayTextSuffix: "", glyph: null, description: "\\\\".ToSymbolDisplayParts(), rules: _itemRules); private CompletionItem CreateUnixRoot() => CommonCompletionItem.Create( "/", displayTextSuffix: "", glyph: _folderGlyph, description: "/".ToSymbolDisplayParts(), rules: _itemRules); private CompletionItem CreateFileSystemEntryItem(string fullPath, bool isDirectory) => CommonCompletionItem.Create( PathUtilities.GetFileName(fullPath), displayTextSuffix: "", glyph: isDirectory ? _folderGlyph : _fileGlyph, description: fullPath.ToSymbolDisplayParts(), rules: _itemRules); private CompletionItem CreateLogicalDriveItem(string drive) => CommonCompletionItem.Create( drive, displayTextSuffix: "", glyph: _folderGlyph, description: drive.ToSymbolDisplayParts(), rules: _itemRules); public Task<ImmutableArray<CompletionItem>> GetItemsAsync(string directoryPath, CancellationToken cancellationToken) => Task.Run(() => GetItems(directoryPath, cancellationToken), cancellationToken); private ImmutableArray<CompletionItem> GetItems(string directoryPath, CancellationToken cancellationToken) { if (!PathUtilities.IsUnixLikePlatform && directoryPath == "\\") { // The user has typed only "\". In this case, we want to add "\\" to the list. return [CreateNetworkRoot()]; } var result = ArrayBuilder<CompletionItem>.GetInstance(); var pathKind = PathUtilities.GetPathKind(directoryPath); switch (pathKind) { case PathKind.Empty: // base directory if (_baseDirectory != null) { result.AddRange(GetItemsInDirectory(_baseDirectory, cancellationToken)); } // roots if (PathUtilities.IsUnixLikePlatform) { result.AddRange(CreateUnixRoot()); } else { foreach (var drive in GetLogicalDrives()) { result.Add(CreateLogicalDriveItem(drive.TrimEnd(s_windowsDirectorySeparator))); } result.Add(CreateNetworkRoot()); } // entries on search paths foreach (var searchPath in _searchPaths) { result.AddRange(GetItemsInDirectory(searchPath, cancellationToken)); } break; case PathKind.Absolute: case PathKind.RelativeToCurrentDirectory: case PathKind.RelativeToCurrentParent: case PathKind.RelativeToCurrentRoot: var fullDirectoryPath = FileUtilities.ResolveRelativePath(directoryPath, basePath: null, baseDirectory: _baseDirectory); if (fullDirectoryPath != null) { result.AddRange(GetItemsInDirectory(fullDirectoryPath, cancellationToken)); } else { // invalid path result.Clear(); } break; case PathKind.Relative: // base directory: if (_baseDirectory != null) { result.AddRange(GetItemsInDirectory(PathUtilities.CombineAbsoluteAndRelativePaths(_baseDirectory, directoryPath)!, cancellationToken)); } // search paths: foreach (var searchPath in _searchPaths) { result.AddRange(GetItemsInDirectory(PathUtilities.CombineAbsoluteAndRelativePaths(searchPath, directoryPath)!, cancellationToken)); } break; case PathKind.RelativeToDriveDirectory: // Paths "C:dir" are not supported, but when the path doesn't include any directory, i.e. "C:", // we return the drive itself. if (directoryPath.Length == 2) { result.Add(CreateLogicalDriveItem(directoryPath)); } break; default: throw ExceptionUtilities.UnexpectedValue(pathKind); } return result.ToImmutableAndFree(); } private IEnumerable<CompletionItem> GetItemsInDirectory(string fullDirectoryPath, CancellationToken cancellationToken) { Debug.Assert(PathUtilities.IsAbsolute(fullDirectoryPath)); cancellationToken.ThrowIfCancellationRequested(); if (!DirectoryExists(fullDirectoryPath)) { yield break; } cancellationToken.ThrowIfCancellationRequested(); foreach (var directory in EnumerateDirectories(fullDirectoryPath)) { if (IsVisibleFileSystemEntry(directory)) { yield return CreateFileSystemEntryItem(directory, isDirectory: true); } } cancellationToken.ThrowIfCancellationRequested(); foreach (var file in EnumerateFiles(fullDirectoryPath)) { if (_allowableExtensions.Length != 0 && !_allowableExtensions.Contains( PathUtilities.GetExtension(file), PathUtilities.IsUnixLikePlatform ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase)) { continue; } cancellationToken.ThrowIfCancellationRequested(); if (IsVisibleFileSystemEntry(file)) { yield return CreateFileSystemEntryItem(file, isDirectory: false); } } } internal TestAccessor GetTestAccessor() => new(this); internal readonly struct TestAccessor(FileSystemCompletionHelper fileSystemCompletionHelper) { private readonly FileSystemCompletionHelper _fileSystemCompletionHelper = fileSystemCompletionHelper; internal ImmutableArray<CompletionItem> GetItems(string directoryPath, CancellationToken cancellationToken) => _fileSystemCompletionHelper.GetItems(directoryPath, cancellationToken); } }