diff --git a/src/Tools/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj b/src/Tools/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj index d0964c328354a..e0e3171327ed3 100644 --- a/src/Tools/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj +++ b/src/Tools/ExternalAccess/FSharp/Microsoft.CodeAnalysis.ExternalAccess.FSharp.csproj @@ -26,6 +26,7 @@ + diff --git a/src/Tools/ExternalAccess/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs b/src/Tools/ExternalAccess/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs new file mode 100644 index 0000000000000..f5e56843ca037 --- /dev/null +++ b/src/Tools/ExternalAccess/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs @@ -0,0 +1,178 @@ +// 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.Composition; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.VisualStudio.LanguageServices.ProjectSystem; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp +{ + internal interface IFSharpWorkspaceProjectContextFactory + { + IFSharpWorkspaceProjectContext CreateProjectContext(string filePath, string uniqueName); + } + + internal interface IFSharpWorkspaceProjectContext : IDisposable + { + string DisplayName { get; set; } + ProjectId Id { get; } + string FilePath { get; } + int ProjectReferenceCount { get; } + bool HasProjectReference(string filePath); + int MetadataReferenceCount { get; } + bool HasMetadataReference(string referencePath); + void SetProjectReferences(IEnumerable projRefs); + void SetMetadataReferences(IEnumerable referencePaths); + void AddMetadataReference(string referencePath); + void AddSourceFile(string path, SourceCodeKind kind); + } + + [Shared] + [Export(typeof(FSharpWorkspaceProjectContextFactory))] + [Export(typeof(IFSharpWorkspaceProjectContextFactory))] + internal sealed class FSharpWorkspaceProjectContextFactory : IFSharpWorkspaceProjectContextFactory + { + private readonly IWorkspaceProjectContextFactory _factory; + private readonly IThreadingContext _threadingContext; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public FSharpWorkspaceProjectContextFactory(IWorkspaceProjectContextFactory factory, IThreadingContext threadingContext) + { + _factory = factory; + _threadingContext = threadingContext; + } + + IFSharpWorkspaceProjectContext IFSharpWorkspaceProjectContextFactory.CreateProjectContext(string filePath, string uniqueName) + => CreateProjectContext(filePath, uniqueName); + + public FSharpWorkspaceProjectContext CreateProjectContext(string filePath, string uniqueName) + => CreateProjectContext( + projectUniqueName: uniqueName, + projectFilePath: filePath, + projectGuid: Guid.NewGuid(), + hierarchy: null, + binOutputPath: null); + + public FSharpWorkspaceProjectContext CreateProjectContext(string projectUniqueName, string projectFilePath, Guid projectGuid, object? hierarchy, string? binOutputPath) + => new(_threadingContext.JoinableTaskFactory.Run(() => _factory.CreateProjectContextAsync( + languageName: LanguageNames.FSharp, + projectUniqueName: projectUniqueName, + projectFilePath: projectFilePath, + projectGuid: projectGuid, + hierarchy, + binOutputPath, + assemblyName: null, + CancellationToken.None))); + } + + internal sealed class FSharpWorkspaceProjectContext : IFSharpWorkspaceProjectContext + { + private readonly IWorkspaceProjectContext _vsProjectContext; + + private ImmutableDictionary _projectReferences; + private ImmutableHashSet _metadataReferences; + + public FSharpWorkspaceProjectContext(IWorkspaceProjectContext vsProjectContext) + { + _vsProjectContext = vsProjectContext; + _projectReferences = ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase); + _metadataReferences = ImmutableHashSet.Create(StringComparer.OrdinalIgnoreCase); + } + + public void Dispose() + => _vsProjectContext.Dispose(); + + public IVsLanguageServiceBuildErrorReporter2? BuildErrorReporter + => _vsProjectContext as IVsLanguageServiceBuildErrorReporter2; + + public string DisplayName + { + get => _vsProjectContext.DisplayName; + set => _vsProjectContext.DisplayName = value; + } + + public string BinOutputPath + { + get => _vsProjectContext.BinOutputPath; + set => _vsProjectContext.BinOutputPath = value; + } + + public ProjectId Id + => _vsProjectContext.Id; + + public string FilePath + => _vsProjectContext.ProjectFilePath; + + public int ProjectReferenceCount + => _projectReferences.Count; + + public bool HasProjectReference(string filePath) + => _projectReferences.ContainsKey(filePath); + + public int MetadataReferenceCount + => _metadataReferences.Count; + + public bool HasMetadataReference(string referencePath) + => _metadataReferences.Contains(referencePath); + + public void SetProjectReferences(IEnumerable projRefs) + { + var builder = ImmutableDictionary.CreateBuilder(); + + foreach (var reference in _projectReferences.Values.Cast()) + { + _vsProjectContext.RemoveProjectReference(reference._vsProjectContext); + } + + foreach (var reference in projRefs.Cast()) + { + _vsProjectContext.AddProjectReference(reference._vsProjectContext, MetadataReferenceProperties.Assembly); + builder.Add(reference.FilePath, reference); + } + + _projectReferences = builder.ToImmutable(); + } + + public void SetMetadataReferences(IEnumerable referencePaths) + { + var builder = ImmutableHashSet.CreateBuilder(); + + foreach (var referencePath in _metadataReferences) + { + RemoveMetadataReference(referencePath); + } + + foreach (var referencePath in referencePaths) + { + AddMetadataReference(referencePath); + builder.Add(referencePath); + } + + _metadataReferences = builder.ToImmutable(); + } + + public void RemoveMetadataReference(string referencePath) + => _vsProjectContext.RemoveMetadataReference(referencePath); + + public void AddMetadataReference(string referencePath) + => _vsProjectContext.AddMetadataReference(referencePath, MetadataReferenceProperties.Assembly); + + public void AddSourceFile(string path, SourceCodeKind kind) + => _vsProjectContext.AddSourceFile(path, sourceCodeKind: kind); + + public void RemoveSourceFile(string path) + => _vsProjectContext.RemoveSourceFile(path); + } +}