diff --git a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs index 4f6136b8db..a4f707587a 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs @@ -49,190 +49,168 @@ type internal RoamingProfileStorageLocation(keyName: string) = [] [, ServiceLayer.Default)>] -type internal FSharpWorkspaceServiceFactory [] (metadataAsSourceService: FSharpMetadataAsSourceService) = +type internal FSharpWorkspaceServiceFactory + [] + (editorOptions: EditorOptions, metadataAsSourceService: FSharpMetadataAsSourceService) = + + let tryGetMetadataSnapshot (workspace: VisualStudioWorkspace) (path, timeStamp) = + try + let md = + LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(workspace, path, timeStamp) + + let amd = (md :?> AssemblyMetadata) + let mmd = amd.GetModules().[0] + let mmr = mmd.GetMetadataReader() + + // "lifetime is timed to Metadata you got from the GetMetadata(...). As long as you hold it strongly, raw + // memory we got from metadata reader will be alive. Once you are done, just let everything go and + // let finalizer handle resource rather than calling Dispose from Metadata directly. It is shared metadata. + // You shouldn't dispose it directly." + + let objToHold = box md + + // We don't expect any ilread WeakByteFile to be created when working in Visual Studio + // Debug.Assert((FSharp.Compiler.AbstractIL.ILBinaryReader.GetStatistics().weakByteFileCount = 0), "Expected weakByteFileCount to be zero when using F# in Visual Studio. Was there a problem reading a .NET binary?") + + Some(objToHold, NativePtr.toNativeInt mmr.MetadataPointer, mmr.MetadataLength) + with ex -> + // We catch all and let the backup routines in the F# compiler find the error + Assert.Exception(ex) + None + + let getSource (workspace: Workspace) filename = + async { + let! ct = Async.CancellationToken + + match workspace.CurrentSolution.TryGetDocumentFromPath filename with + | ValueSome document -> + let! text = document.GetTextAsync(ct) |> Async.AwaitTask + return Some(text.ToFSharpSourceText()) + | ValueNone -> return None + } + + let enableParallelReferenceResolution = + editorOptions.LanguageServicePerformance.EnableParallelReferenceResolution + + let enableLiveBuffers = editorOptions.Advanced.IsUseLiveBuffersEnabled + + let enableInMemoryCrossProjectReferences = + editorOptions.LanguageServicePerformance.EnableInMemoryCrossProjectReferences + + let enableFastFindReferences = + editorOptions.LanguageServicePerformance.EnableFastFindReferencesAndRename + + let isInlineParameterNameHintsEnabled = + editorOptions.Advanced.IsInlineParameterNameHintsEnabled + + let isInlineTypeHintsEnabled = editorOptions.Advanced.IsInlineTypeHintsEnabled + + let isInlineReturnTypeHintsEnabled = + editorOptions.Advanced.IsInlineReturnTypeHintsEnabled + + let enablePartialTypeChecking = + editorOptions.LanguageServicePerformance.EnablePartialTypeChecking + + // Default should be false + let keepAllBackgroundResolutions = + editorOptions.LanguageServicePerformance.KeepAllBackgroundResolutions + + // Default should be false + let keepAllBackgroundSymbolUses = + editorOptions.LanguageServicePerformance.KeepAllBackgroundSymbolUses + + // Default should be true + let enableBackgroundItemKeyStoreAndSemanticClassification = + editorOptions.LanguageServicePerformance.EnableBackgroundItemKeyStoreAndSemanticClassification + + let useTransparentCompiler = editorOptions.Advanced.UseTransparentCompiler + + // Default is false here + let solutionCrawler = editorOptions.Advanced.SolutionBackgroundAnalysis + + let create getSource tryGetMetadataSnapshot = + + use _eventDuration = + TelemetryReporter.ReportSingleEventWithDuration( + TelemetryEvents.LanguageServiceStarted, + [| + nameof enableLiveBuffers, enableLiveBuffers + nameof enableParallelReferenceResolution, enableParallelReferenceResolution + nameof enableInMemoryCrossProjectReferences, enableInMemoryCrossProjectReferences + nameof enableFastFindReferences, enableFastFindReferences + nameof isInlineParameterNameHintsEnabled, isInlineParameterNameHintsEnabled + nameof isInlineTypeHintsEnabled, isInlineTypeHintsEnabled + nameof isInlineReturnTypeHintsEnabled, isInlineReturnTypeHintsEnabled + nameof enablePartialTypeChecking, enablePartialTypeChecking + nameof keepAllBackgroundResolutions, keepAllBackgroundResolutions + nameof keepAllBackgroundSymbolUses, keepAllBackgroundSymbolUses + nameof enableBackgroundItemKeyStoreAndSemanticClassification, enableBackgroundItemKeyStoreAndSemanticClassification + "captureIdentifiersWhenParsing", enableFastFindReferences + nameof useTransparentCompiler, useTransparentCompiler + nameof solutionCrawler, solutionCrawler + |], + TelemetryThrottlingStrategy.NoThrottling + ) - // We have a lock just in case if multi-threads try to create a new IFSharpWorkspaceService - - // but we only want to have a single instance of the FSharpChecker regardless if there are multiple instances of IFSharpWorkspaceService. - // In VS, we only ever have a single IFSharpWorkspaceService, but for testing we may have multiple; we still only want a - // single FSharpChecker instance shared across them. - static let gate = obj () + let checker = + FSharpChecker.Create( + projectCacheSize = 5000, // We do not care how big the cache is. VS will actually tell FCS to clear caches, so this is fine. + keepAllBackgroundResolutions = keepAllBackgroundResolutions, + legacyReferenceResolver = LegacyMSBuildReferenceResolver.getResolver (), + tryGetMetadataSnapshot = tryGetMetadataSnapshot, + keepAllBackgroundSymbolUses = keepAllBackgroundSymbolUses, + enableBackgroundItemKeyStoreAndSemanticClassification = enableBackgroundItemKeyStoreAndSemanticClassification, + enablePartialTypeChecking = enablePartialTypeChecking, + parallelReferenceResolution = enableParallelReferenceResolution, + captureIdentifiersWhenParsing = enableFastFindReferences, + documentSource = + (if enableLiveBuffers then + (DocumentSource.Custom(fun filename -> + async { + match! getSource filename with + | Some source -> return Some(source :> ISourceText) + | None -> return None + })) + else + DocumentSource.FileSystem), + useTransparentCompiler = useTransparentCompiler + ) - // We only ever want to have a single FSharpChecker. - static let mutable checkerSingleton = None + checker interface IWorkspaceServiceFactory with - member _.CreateService(workspaceServices) = + member _.CreateService(workspaceServices) = let workspace = workspaceServices.Workspace + let getSource = getSource workspace - let tryGetMetadataSnapshot (path, timeStamp) = + let tryGetMetadataSnapshot = match workspace with - | :? VisualStudioWorkspace as workspace -> - try - let md = - LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(workspace, path, timeStamp) - - let amd = (md :?> AssemblyMetadata) - let mmd = amd.GetModules().[0] - let mmr = mmd.GetMetadataReader() - - // "lifetime is timed to Metadata you got from the GetMetadata(...). As long as you hold it strongly, raw - // memory we got from metadata reader will be alive. Once you are done, just let everything go and - // let finalizer handle resource rather than calling Dispose from Metadata directly. It is shared metadata. - // You shouldn't dispose it directly." - - let objToHold = box md - - // We don't expect any ilread WeakByteFile to be created when working in Visual Studio - // Debug.Assert((FSharp.Compiler.AbstractIL.ILBinaryReader.GetStatistics().weakByteFileCount = 0), "Expected weakByteFileCount to be zero when using F# in Visual Studio. Was there a problem reading a .NET binary?") - - Some(objToHold, NativePtr.toNativeInt mmr.MetadataPointer, mmr.MetadataLength) - with ex -> - // We catch all and let the backup routines in the F# compiler find the error - Assert.Exception(ex) - None - | _ -> None - - let getSource filename = - async { - let! ct = Async.CancellationToken - - match workspace.CurrentSolution.TryGetDocumentFromPath filename with - | ValueSome document -> - let! text = document.GetTextAsync(ct) |> Async.AwaitTask - return Some(text.ToFSharpSourceText()) - | ValueNone -> return None - } + | :? VisualStudioWorkspace as workspace -> tryGetMetadataSnapshot workspace + | _ -> fun _ -> None - lock gate (fun () -> - match checkerSingleton with - | Some _ -> () - | _ -> - let checker = - lazy - let editorOptions = workspace.Services.GetService() - - let enableParallelReferenceResolution = - editorOptions.LanguageServicePerformance.EnableParallelReferenceResolution - - let enableLiveBuffers = editorOptions.Advanced.IsUseLiveBuffersEnabled - - let enableInMemoryCrossProjectReferences = - editorOptions.LanguageServicePerformance.EnableInMemoryCrossProjectReferences - - let enableFastFindReferences = - editorOptions.LanguageServicePerformance.EnableFastFindReferencesAndRename - - let isInlineParameterNameHintsEnabled = - editorOptions.Advanced.IsInlineParameterNameHintsEnabled - - let isInlineTypeHintsEnabled = editorOptions.Advanced.IsInlineTypeHintsEnabled - - let isInlineReturnTypeHintsEnabled = - editorOptions.Advanced.IsInlineReturnTypeHintsEnabled - - let enablePartialTypeChecking = - editorOptions.LanguageServicePerformance.EnablePartialTypeChecking - - // Default should be false - let keepAllBackgroundResolutions = - editorOptions.LanguageServicePerformance.KeepAllBackgroundResolutions - - // Default should be false - let keepAllBackgroundSymbolUses = - editorOptions.LanguageServicePerformance.KeepAllBackgroundSymbolUses - - // Default should be true - let enableBackgroundItemKeyStoreAndSemanticClassification = - editorOptions.LanguageServicePerformance.EnableBackgroundItemKeyStoreAndSemanticClassification - - let useTransparentCompiler = editorOptions.Advanced.UseTransparentCompiler - - // Default is false here - let solutionCrawler = editorOptions.Advanced.SolutionBackgroundAnalysis - - use _eventDuration = - TelemetryReporter.ReportSingleEventWithDuration( - TelemetryEvents.LanguageServiceStarted, - [| - nameof enableLiveBuffers, enableLiveBuffers - nameof enableParallelReferenceResolution, enableParallelReferenceResolution - nameof enableInMemoryCrossProjectReferences, enableInMemoryCrossProjectReferences - nameof enableFastFindReferences, enableFastFindReferences - nameof isInlineParameterNameHintsEnabled, isInlineParameterNameHintsEnabled - nameof isInlineTypeHintsEnabled, isInlineTypeHintsEnabled - nameof isInlineReturnTypeHintsEnabled, isInlineReturnTypeHintsEnabled - nameof enablePartialTypeChecking, enablePartialTypeChecking - nameof keepAllBackgroundResolutions, keepAllBackgroundResolutions - nameof keepAllBackgroundSymbolUses, keepAllBackgroundSymbolUses - nameof enableBackgroundItemKeyStoreAndSemanticClassification, - enableBackgroundItemKeyStoreAndSemanticClassification - "captureIdentifiersWhenParsing", enableFastFindReferences - nameof useTransparentCompiler, useTransparentCompiler - nameof solutionCrawler, solutionCrawler - |], - TelemetryThrottlingStrategy.NoThrottling - ) - - let checker = - FSharpChecker.Create( - projectCacheSize = 5000, // We do not care how big the cache is. VS will actually tell FCS to clear caches, so this is fine. - keepAllBackgroundResolutions = keepAllBackgroundResolutions, - legacyReferenceResolver = LegacyMSBuildReferenceResolver.getResolver (), - tryGetMetadataSnapshot = tryGetMetadataSnapshot, - keepAllBackgroundSymbolUses = keepAllBackgroundSymbolUses, - enableBackgroundItemKeyStoreAndSemanticClassification = - enableBackgroundItemKeyStoreAndSemanticClassification, - enablePartialTypeChecking = enablePartialTypeChecking, - parallelReferenceResolution = enableParallelReferenceResolution, - captureIdentifiersWhenParsing = enableFastFindReferences, - documentSource = - (if enableLiveBuffers then - (DocumentSource.Custom(fun filename -> - async { - match! getSource filename with - | Some source -> return Some(source :> ISourceText) - | None -> return None - })) - else - DocumentSource.FileSystem), - useTransparentCompiler = useTransparentCompiler - ) - - if enableLiveBuffers && not useTransparentCompiler then - workspace.WorkspaceChanged.Add(fun args -> - if args.DocumentId <> null then - cancellableTask { - let document = args.NewSolution.GetDocument(args.DocumentId) - - let! _, _, _, options = - document.GetFSharpCompilationOptionsAsync(nameof (workspace.WorkspaceChanged)) - - do! checker.NotifyFileChanged(document.FilePath, options) - } - |> CancellableTask.startAsTask CancellationToken.None - |> ignore) - - checker - - checkerSingleton <- Some checker) - - let optionsManager = - lazy - match checkerSingleton with - | Some checker -> FSharpProjectOptionsManager(checker.Value, workspaceServices.Workspace) - | _ -> failwith "Checker not set." + let checker = create getSource tryGetMetadataSnapshot - { new IFSharpWorkspaceService with - member _.Checker = - match checkerSingleton with - | Some checker -> checker.Value - | _ -> failwith "Checker not set." + if enableLiveBuffers && not useTransparentCompiler then + workspace.WorkspaceChanged.Add(fun args -> + if args.DocumentId <> null then + cancellableTask { + let document = args.NewSolution.GetDocument(args.DocumentId) - member _.FSharpProjectOptionsManager = optionsManager.Value + let! _, _, _, options = document.GetFSharpCompilationOptionsAsync(nameof (workspace.WorkspaceChanged)) + + do! checker.NotifyFileChanged(document.FilePath, options) + } + |> CancellableTask.startAsTask CancellationToken.None + |> ignore) + + let optionsManager = FSharpProjectOptionsManager(checker, workspace) + + { new IFSharpWorkspaceService with + member _.Checker = checker + member _.FSharpProjectOptionsManager = optionsManager member _.MetadataAsSource = metadataAsSourceService } - :> _ [] type private FSharpSolutionEvents(projectManager: FSharpProjectOptionsManager, metadataAsSource: FSharpMetadataAsSourceService) = @@ -359,15 +337,12 @@ type internal FSharpPackage() as this = base.RegisterInitializationWork(packageRegistrationTasks: PackageRegistrationTasks) packageRegistrationTasks.AddTask( - true, - (fun progress _tasks cancellationToken -> - foregroundCancellableTask { + false, + (fun _progress _tasks cancellationToken -> + cancellableTask { let! commandService = this.GetServiceAsync(typeof) let commandService = commandService :?> OleMenuCommandService - // Switch to UI thread - do! this.JoinableTaskFactory.SwitchToMainThreadAsync() - // FSI-LINKAGE-POINT: sited init FSharp.Interactive.Hooks.fsiConsoleWindowPackageInitializeSited (this :> Package) commandService @@ -375,16 +350,15 @@ type internal FSharpPackage() as this = let _fsiPropertyPage = this.GetDialogPage(typeof) - let workspace = this.ComponentModel.GetService() + let workspace = + this.ComponentModel.DefaultExportProvider.GetExportedValue() - let _ = - this.ComponentModel.DefaultExportProvider.GetExport() + let fsharpWorkspaceService = + workspace.Services.GetService() - let optionsManager = - workspace.Services.GetService().FSharpProjectOptionsManager + let optionsManager = fsharpWorkspaceService.FSharpProjectOptionsManager - let metadataAsSource = - this.ComponentModel.DefaultExportProvider.GetExport().Value + let metadataAsSource = fsharpWorkspaceService.MetadataAsSource let! solution = this.GetServiceAsync(typeof) let solution = solution :?> IVsSolution