diff --git a/src/fsharp/vs/IncrementalBuild.fs b/src/fsharp/vs/IncrementalBuild.fs index 5110eda78eae..482e77c67846 100755 --- a/src/fsharp/vs/IncrementalBuild.fs +++ b/src/fsharp/vs/IncrementalBuild.fs @@ -1272,10 +1272,10 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig let tcConfigP = TcConfigProvider.Constant(tcConfig) let importsInvalidated = new Event() - let fileParsed = new Event<_>() - let beforeTypeCheckFile = new Event<_>() - let fileChecked = new Event<_>() - let projectChecked = new Event<_>() + let fileParsed = new Event() + let beforeFileChecked = new Event() + let fileChecked = new Event() + let projectChecked = new Event() // Resolve assemblies and create the framework TcImports. This is done when constructing the // builder itself, rather than as an incremental task. This caches a level of "system" references. No type providers are @@ -1361,7 +1361,7 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig try IncrementalBuilderEventTesting.MRU.Add(IncrementalBuilderEventTesting.IBEParsed filename) let result = ParseOneInputFile(tcConfig,lexResourceManager, [], filename ,isLastCompiland,errorLogger,(*retryLocked*)true) - fileParsed.Trigger filename + fileParsed.Trigger (filename) result,sourceRange,filename,errorLogger.GetErrors () with exn -> System.Diagnostics.Debug.Assert(false, sprintf "unexpected failure in IncrementalFSharpBuild.Parse\nerror = %s" (exn.ToString())) @@ -1457,7 +1457,7 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig let errorLogger = GetErrorLoggerFilteringByScopedPragmas(false,GetScopedPragmasForInput(input),capturingErrorLogger) let fullComputation = eventually { - beforeTypeCheckFile.Trigger filename + beforeFileChecked.Trigger (filename) ApplyMetaCommandsFromInputToTcConfig tcConfig (input, Path.GetDirectoryName filename) |> ignore let sink = TcResultsSinkImpl(tcAcc.tcGlobals) @@ -1475,7 +1475,7 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig let typedImplFiles = if keepAssemblyContents then typedImplFiles else [] let tcResolutions = if keepAllBackgroundResolutions then sink.GetResolutions() else TcResolutions.Empty let tcSymbolUses = sink.GetSymbolUses() - fileChecked.Trigger filename + fileChecked.Trigger (filename) return {tcAcc with tcState=tcState tcEnvAtEndOfFile=tcEnvAtEndOfFile topAttribs=Some topAttribs @@ -1656,7 +1656,7 @@ type IncrementalBuilder(frameworkTcImportsCache: FrameworkImportsCache, tcConfig member __.TcConfig = tcConfig member __.FileParsed = fileParsed.Publish - member __.BeforeTypeCheckFile = beforeTypeCheckFile.Publish + member __.BeforeFileChecked = beforeFileChecked.Publish member __.FileChecked = fileChecked.Publish member __.ProjectChecked = projectChecked.Publish member __.ImportedCcusInvalidated = importsInvalidated.Publish diff --git a/src/fsharp/vs/IncrementalBuild.fsi b/src/fsharp/vs/IncrementalBuild.fsi index 5698b25b3699..6ca31a191ead 100755 --- a/src/fsharp/vs/IncrementalBuild.fsi +++ b/src/fsharp/vs/IncrementalBuild.fsi @@ -122,7 +122,7 @@ type internal IncrementalBuilder = /// Raised just before a file is type-checked, to invalidate the state of the file in VS and force VS to request a new direct typecheck of the file. /// The incremental builder also typechecks the file (error and intellisense results from the backgroud builder are not /// used by VS). - member BeforeTypeCheckFile : IEvent + member BeforeFileChecked : IEvent /// Raised just after a file is parsed member FileParsed : IEvent diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index e31c120fc738..199c46318af7 100755 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -1732,6 +1732,7 @@ type FSharpProjectOptions = UseScriptResolutionRules : bool LoadTime : System.DateTime UnresolvedReferences : UnresolvedReferencesSet option + ExtraProjectInfo : obj option } member x.ProjectOptions = x.OtherOptions /// Whether the two parse options refer to the same project. @@ -2048,10 +2049,10 @@ module Helpers = type BackgroundCompiler(referenceResolver, projectCacheSize, keepAssemblyContents, keepAllBackgroundResolutions) as self = // STATIC ROOT: FSharpLanguageServiceTestable.FSharpChecker.backgroundCompiler.reactor: The one and only Reactor let reactor = Reactor.Singleton - let beforeFileChecked = Event() - let fileParsed = Event() - let fileChecked = Event() - let projectChecked = Event() + let beforeFileChecked = Event() + let fileParsed = Event() + let fileChecked = Event() + let projectChecked = Event() let mutable implicitlyStartBackgroundWork = true let reactorOps = @@ -2105,10 +2106,10 @@ type BackgroundCompiler(referenceResolver, projectCacheSize, keepAssemblyContent // // This indicates to the UI that the file type check state is dirty. If the file is open and visible then // the UI will sooner or later request a typecheck of the file, recording errors and intellisense information. - builder.BeforeTypeCheckFile.Add (beforeFileChecked.Trigger) - builder.FileParsed.Add (fileParsed.Trigger) - builder.FileChecked.Add (fileChecked.Trigger) - builder.ProjectChecked.Add (fun () -> projectChecked.Trigger options.ProjectFileName) + builder.BeforeFileChecked.Add (fun file -> beforeFileChecked.Trigger(file, options.ExtraProjectInfo)) + builder.FileParsed.Add (fun file -> fileParsed.Trigger(file, options.ExtraProjectInfo)) + builder.FileChecked.Add (fun file -> fileChecked.Trigger(file, options.ExtraProjectInfo)) + builder.ProjectChecked.Add (fun () -> projectChecked.Trigger (options.ProjectFileName, options.ExtraProjectInfo)) (builderOpt, errorsAndWarnings, decrement) @@ -2438,7 +2439,7 @@ type BackgroundCompiler(referenceResolver, projectCacheSize, keepAssemblyContent member bc.ParseAndCheckProject(options) = reactor.EnqueueAndAwaitOpAsync("ParseAndCheckProject " + options.ProjectFileName, fun ct -> bc.ParseAndCheckProjectImpl(options, ct)) - member bc.GetProjectOptionsFromScript(filename, source, ?loadedTimeStamp, ?otherFlags, ?useFsiAuxLib, ?assumeDotNetFramework) = + member bc.GetProjectOptionsFromScript(filename, source, ?loadedTimeStamp, ?otherFlags, ?useFsiAuxLib, ?assumeDotNetFramework, ?extraProjectInfo: obj) = reactor.EnqueueAndAwaitOpAsync ("GetProjectOptionsFromScript " + filename, fun _ct -> // Do we add a reference to FSharp.Compiler.Interactive.Settings by default? let useFsiAuxLib = defaultArg useFsiAuxLib true @@ -2473,6 +2474,7 @@ type BackgroundCompiler(referenceResolver, projectCacheSize, keepAssemblyContent UseScriptResolutionRules = true LoadTime = loadedTimeStamp UnresolvedReferences = Some (UnresolvedReferencesSet(fas.UnresolvedReferences)) + ExtraProjectInfo=extraProjectInfo } scriptClosureCache.Set(co,fas) // Save the full load closure for later correlation. co) @@ -2664,10 +2666,10 @@ type FSharpChecker(referenceResolver, projectCacheSize, keepAssemblyContents, ke backgroundCompiler.KeepProjectAlive(options) /// For a given script file, get the ProjectOptions implied by the #load closure - member ic.GetProjectOptionsFromScript(filename, source, ?loadedTimeStamp, ?otherFlags, ?useFsiAuxLib) = - backgroundCompiler.GetProjectOptionsFromScript(filename,source,?loadedTimeStamp=loadedTimeStamp, ?otherFlags=otherFlags, ?useFsiAuxLib=useFsiAuxLib) + member ic.GetProjectOptionsFromScript(filename, source, ?loadedTimeStamp, ?otherFlags, ?useFsiAuxLib, ?extraProjectInfo: obj) = + backgroundCompiler.GetProjectOptionsFromScript(filename,source,?loadedTimeStamp=loadedTimeStamp, ?otherFlags=otherFlags, ?useFsiAuxLib=useFsiAuxLib, ?extraProjectInfo=extraProjectInfo) - member ic.GetProjectOptionsFromCommandLineArgs(projectFileName, argv, ?loadedTimeStamp) = + member ic.GetProjectOptionsFromCommandLineArgs(projectFileName, argv, ?loadedTimeStamp, ?extraProjectInfo: obj) = let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading { ProjectFileName = projectFileName ProjectFileNames = [| |] // the project file names will be inferred from the ProjectOptions @@ -2676,7 +2678,8 @@ type FSharpChecker(referenceResolver, projectCacheSize, keepAssemblyContents, ke IsIncompleteTypeCheckEnvironment = false UseScriptResolutionRules = false LoadTime = loadedTimeStamp - UnresolvedReferences = None } + UnresolvedReferences = None + ExtraProjectInfo=extraProjectInfo } /// Begin background parsing the given project. member ic.StartBackgroundCompile(options) = backgroundCompiler.CheckProjectInBackground(options) diff --git a/src/fsharp/vs/service.fsi b/src/fsharp/vs/service.fsi index d978cafc41ef..e844410f2d17 100755 --- a/src/fsharp/vs/service.fsi +++ b/src/fsharp/vs/service.fsi @@ -318,6 +318,8 @@ type internal FSharpProjectOptions = LoadTime : DateTime /// Unused in this API and should be 'None' UnresolvedReferences : UnresolvedReferencesSet option + /// Extra information passed back on event trigger + ExtraProjectInfo : obj option } @@ -473,7 +475,7 @@ type internal FSharpChecker = /// Indicates when the script was loaded into the editing environment, /// so that an 'unload' and 'reload' action will cause the script to be considered as a new project, /// so that references are re-resolved. - member GetProjectOptionsFromScript : filename: string * source: string * ?loadedTimeStamp: DateTime * ?otherFlags: string[] * ?useFsiAuxLib: bool -> Async + member GetProjectOptionsFromScript : filename: string * source: string * ?loadedTimeStamp: DateTime * ?otherFlags: string[] * ?useFsiAuxLib: bool * ?extraProjectInfo: obj -> Async /// /// Get the FSharpProjectOptions implied by a set of command line arguments. @@ -484,7 +486,7 @@ type internal FSharpChecker = /// Indicates when the script was loaded into the editing environment, /// so that an 'unload' and 'reload' action will cause the script to be considered as a new project, /// so that references are re-resolved. - member GetProjectOptionsFromCommandLineArgs : projectFileName: string * argv: string[] * ?loadedTimeStamp: DateTime -> FSharpProjectOptions + member GetProjectOptionsFromCommandLineArgs : projectFileName: string * argv: string[] * ?loadedTimeStamp: DateTime * ?extraProjectInfo: obj -> FSharpProjectOptions /// /// Like ParseFileInProject, but uses results from the background builder. @@ -558,17 +560,17 @@ type internal FSharpChecker = /// and that the file has become eligible to be re-typechecked for errors. /// /// The event will be raised on a background thread. - member BeforeBackgroundFileCheck : IEvent + member BeforeBackgroundFileCheck : IEvent /// Raised after a parse of a file in the background analysis. /// /// The event will be raised on a background thread. - member FileParsed : IEvent + member FileParsed : IEvent /// Raised after a check of a file in the background analysis. /// /// The event will be raised on a background thread. - member FileChecked : IEvent + member FileChecked : IEvent /// Get or set a flag which controls if background work is started implicitly. /// @@ -583,7 +585,7 @@ type internal FSharpChecker = /// Notify the host that a project has been fully checked in the background (using file contents provided by the file system API) /// /// The event may be raised on a background thread. - member ProjectChecked : IEvent + member ProjectChecked : IEvent // For internal use only member internal ReactorOps : IReactorOperations diff --git a/vsintegration/src/FSharp.Editor/BraceMatchingService.fs b/vsintegration/src/FSharp.Editor/BraceMatchingService.fs index 359fcf4b3d15..776aeeab0716 100644 --- a/vsintegration/src/FSharp.Editor/BraceMatchingService.fs +++ b/vsintegration/src/FSharp.Editor/BraceMatchingService.fs @@ -23,7 +23,8 @@ type internal FSharpBraceMatchingService() = interface IBraceMatcher with member this.FindBracesAsync(document, position, cancellationToken) = async { - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! result = FSharpBraceMatchingService.GetBraceMatchingResult(sourceText, document.Name, options, position) diff --git a/vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs b/vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs index db7e405d799e..dda515e3fb5a 100644 --- a/vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs +++ b/vsintegration/src/FSharp.Editor/BreakpointResolutionService.fs @@ -48,7 +48,8 @@ type internal FSharpBreakpointResolutionService() = interface IBreakpointResolutionService with member this.ResolveBreakpointAsync(document: Document, textSpan: TextSpan, cancellationToken: CancellationToken): Task = async { - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! location = FSharpBreakpointResolutionService.GetBreakpointLocation(sourceText, document.Name, textSpan, options) diff --git a/vsintegration/src/FSharp.Editor/ColorizationService.fs b/vsintegration/src/FSharp.Editor/ColorizationService.fs index 9060eb0b2493..20a8b5ea85b4 100644 --- a/vsintegration/src/FSharp.Editor/ColorizationService.fs +++ b/vsintegration/src/FSharp.Editor/ColorizationService.fs @@ -36,9 +36,10 @@ type internal FSharpColorizationService() = member this.AddSyntacticClassificationsAsync(document: Document, textSpan: TextSpan, result: List, cancellationToken: CancellationToken) = async { - let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) result.AddRange(CommonHelpers.getColorizationData( document.Id, sourceText, textSpan, Some(document.FilePath), defines, cancellationToken)) @@ -47,7 +48,8 @@ type internal FSharpColorizationService() = member this.AddSemanticClassificationsAsync(document: Document, textSpan: TextSpan, result: List, cancellationToken: CancellationToken) = async { - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask diff --git a/vsintegration/src/FSharp.Editor/CommonHelpers.fs b/vsintegration/src/FSharp.Editor/CommonHelpers.fs index c177ff13e469..7a753f4b500e 100644 --- a/vsintegration/src/FSharp.Editor/CommonHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CommonHelpers.fs @@ -153,7 +153,9 @@ module CommonHelpers = | None -> () result - with ex -> + with + | :? System.OperationCanceledException -> reraise() + | ex -> Assert.Exception(ex) List() diff --git a/vsintegration/src/FSharp.Editor/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/CompletionProvider.fs index 819eec560b18..da180312228f 100644 --- a/vsintegration/src/FSharp.Editor/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/CompletionProvider.fs @@ -137,17 +137,20 @@ type internal FSharpCompletionProvider(workspace: Workspace, serviceProvider: SV let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container) let document = workspace.CurrentSolution.GetDocument(documentId) + // We use RunSynchronously here, which gets the + let optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) |> Async.RunSynchronously let defines = - match FSharpLanguageService.GetOptions(document.Project.Id) with + match optionsOpt with | None -> [] | Some(options) -> CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) - document.Id, document.FilePath, defines + (document.Id, document.FilePath, defines) FSharpCompletionProvider.ShouldTriggerCompletionAux(sourceText, caretPosition, trigger.Kind, getInfo) override this.ProvideCompletionsAsync(context: Microsoft.CodeAnalysis.Completion.CompletionContext) = async { - match FSharpLanguageService.GetOptions(context.Document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(context.Document) + match optionsOpt with | Some(options) -> let! sourceText = context.Document.GetTextAsync(context.CancellationToken) |> Async.AwaitTask let! textVersion = context.Document.GetTextVersionAsync(context.CancellationToken) |> Async.AwaitTask diff --git a/vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs index 3de82d3f625b..4fef2741a97b 100644 --- a/vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/DocumentDiagnosticAnalyzer.fs @@ -25,6 +25,8 @@ type internal FSharpDocumentDiagnosticAnalyzer() = inherit DocumentDiagnosticAnalyzer() static member GetDiagnostics(filePath: string, sourceText: SourceText, textVersionHash: int, options: FSharpProjectOptions, addSemanticErrors: bool) = async { + + let! parseResults = FSharpLanguageService.Checker.ParseFileInProject(filePath, sourceText.ToString(), options) let! errors = async { if addSemanticErrors then @@ -57,7 +59,8 @@ type internal FSharpDocumentDiagnosticAnalyzer() = override this.AnalyzeSyntaxAsync(document: Document, cancellationToken: CancellationToken): Task> = async { - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask @@ -68,7 +71,8 @@ type internal FSharpDocumentDiagnosticAnalyzer() = override this.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken): Task> = async { - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask @@ -76,3 +80,16 @@ type internal FSharpDocumentDiagnosticAnalyzer() = | None -> return ImmutableArray.Empty } |> CommonRoslynHelpers.StartAsyncAsTask cancellationToken + +(* +[] +type internal FSharpDiagnosticAnalyzer() = + inherit DiagnosticAnalyzer() + + override this.SupportedDiagnostics = CommonRoslynHelpers.SupportedDiagnostics() + + override this.Initialize(context) = + context.RegisterCompilationStartAction(fun ctxt -> ()) + context.RegisterCompilationAction(fun ctxt -> ()) + context.RegisterOperationAction(fun ctxt -> ()) +*) \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs index 3b03624316c0..cdd50f6c957e 100644 --- a/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/GoToDefinitionService.fs @@ -77,7 +77,8 @@ type internal FSharpGoToDefinitionService [] ([() - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask diff --git a/vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs b/vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs index dab7714df06b..49c1c811ee3d 100644 --- a/vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageDebugInfoService.fs @@ -66,7 +66,8 @@ type internal FSharpLanguageDebugInfoService() = member this.GetDataTipInfoAsync(document: Document, position: int, cancellationToken: CancellationToken): Task = async { - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask diff --git a/vsintegration/src/FSharp.Editor/LanguageService.fs b/vsintegration/src/FSharp.Editor/LanguageService.fs index 81cf5743ded0..4919024d9e1c 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService.fs @@ -48,20 +48,90 @@ type internal SVsSettingsPersistenceManager = class end type internal FSharpLanguageService(package : FSharpPackage) = inherit AbstractLanguageService(package) - static let optionsCache = Dictionary() - static let checker = lazy FSharpChecker.Create() - static member Checker with get() = checker.Value + // A table of information about projects, excluding single-file projects. + static let projectTable = Dictionary() + + // A table of information about single-file projects. Currently we only need the load time of each such file. + static let singleFileProjectTable = Dictionary() + + static let checker = + lazy + let checker = FSharpChecker.Create() + + // Add a hook so that when the background builder refreshes the background semantic build for a file, we refresh + // the editor. + checker.BeforeBackgroundFileCheck.Add(fun (fileName, extraProjectInfo) -> + async { + try + match extraProjectInfo with + | Some (:? Workspace as workspace) -> + let solution = workspace.CurrentSolution + let documentIds = solution.GetDocumentIdsWithFilePath(fileName) + if not documentIds.IsEmpty then + let documentId = documentIds |> Seq.head + let document = solution.GetDocument(documentId) + let! sourceText = document.GetTextAsync() |> Async.AwaitTask + let newSolution = solution.WithDocumentText(documentId, sourceText (* , PreservationMode.PreserveIdentity *)) + workspace.TryApplyChanges(newSolution) |> ignore + | _ -> () + with ex -> + Assert.Exception(ex) + } |> Async.StartImmediate + ) + + checker + + + static let UpdateOptions(projectId: ProjectId, site: IProjectSite, workspace: Workspace) = + let extraProjectInfo = Some(box workspace) + let options = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(site, site.ProjectFileName(), extraProjectInfo) + checker.Value.InvalidateConfiguration(options) + projectTable.[projectId] <- options + + static let ClearOptions(projectId: ProjectId) = + projectTable.Remove(projectId) |> ignore + + static let ComputeSingleFileOptions (fileName, loadTime, fileContents, workspace: Workspace) = async { + let extraProjectInfo = Some(box workspace) + if SourceFile.MustBeSingleFileProject(fileName) then + let! options = checker.Value.GetProjectOptionsFromScript(fileName, fileContents, loadTime, [| |], ?extraProjectInfo=extraProjectInfo) + let site = ProjectSitesAndFiles.CreateProjectSiteForScript(fileName, options) + return ProjectSitesAndFiles.GetProjectOptionsForProjectSite(site,fileName,options.ExtraProjectInfo) + else + let site = ProjectSitesAndFiles.ProjectSiteOfSingleFile(fileName) + return ProjectSitesAndFiles.GetProjectOptionsForProjectSite(site,fileName,extraProjectInfo) + } - static member GetOptions(projectId: ProjectId) = - if optionsCache.ContainsKey(projectId) then - Some(optionsCache.[projectId]) + static member GetOptionsForProject(projectId: ProjectId) = + if projectTable.ContainsKey(projectId) then + Some(projectTable.[projectId]) else None - static member UpdateOptions(projectId: ProjectId, options: FSharpProjectOptions) = - optionsCache.[projectId] <- options + static member GetOptionsForDocumentOrProject(document: Document) = async { + let projectId = document.Project.Id + + // The options for a single-file script project are re-requested each time the file is analyzed. This is because the + // single-file project may contain #load and #r references which are changing as the user edits, and we may need to re-analyze + // to determine the latest settings. FCS keeps a cache to help ensure these are up-to-date. + if singleFileProjectTable.ContainsKey(projectId) then + try + let loadTime = singleFileProjectTable.[projectId] + let fileName = document.FilePath + let! cancellationToken = Async.CancellationToken + let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask + let! options = ComputeSingleFileOptions (fileName, loadTime, sourceText.ToString(), document.Project.Solution.Workspace) + return Some options + with ex -> + Assert.Exception(ex) + return None + else return FSharpLanguageService.GetOptionsForProject(projectId) + } + + static member Checker with get() = checker.Value member this.SyncProject(project: AbstractProject, projectContext: IWorkspaceProjectContext, site: IProjectSite) = + let hashSetIgnoreCase x = new HashSet(x, StringComparer.OrdinalIgnoreCase) let updatedFiles = site.SourceFilesOnDisk() |> hashSetIgnoreCase let workspaceFiles = project.GetCurrentDocuments() |> Seq.map(fun file -> file.FilePath) |> hashSetIgnoreCase @@ -78,8 +148,7 @@ type internal FSharpLanguageService(package : FSharpPackage) = // update the cached options if updated then - let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(site, "") - FSharpLanguageService.UpdateOptions(project.Id, checkOptions) + UpdateOptions(project.Id, site, project.Workspace) override this.ContentTypeName = FSharpCommonConstants.FSharpContentTypeName override this.LanguageName = FSharpCommonConstants.FSharpLanguageName @@ -96,7 +165,7 @@ type internal FSharpLanguageService(package : FSharpPackage) = // FSROSLYNTODO: Hide navigation bars for now. Enable after adding tests workspace.Options <- workspace.Options.WithChangedOption(NavigationBarOptions.ShowNavigationBar, FSharpCommonConstants.FSharpLanguageName, false) - + match textView.GetBuffer() with | (VSConstants.S_OK, textLines) -> let filename = VsTextLines.GetFilename textLines @@ -118,8 +187,7 @@ type internal FSharpLanguageService(package : FSharpPackage) = let projectFileName = site.ProjectFileName() let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectFileName) - let options = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(site, site.ProjectFileName()) - FSharpLanguageService.UpdateOptions(projectId, options) + UpdateOptions(projectId, site, workspace) match workspace.ProjectTracker.GetProject(projectId) with | null -> @@ -131,14 +199,16 @@ type internal FSharpLanguageService(package : FSharpPackage) = this.SyncProject(project, projectContext, site) site.AdviseProjectSiteChanges(FSharpCommonConstants.FSharpLanguageServiceCallbackName, AdviseProjectSiteChanges(fun () -> this.SyncProject(project, projectContext, site))) - site.AdviseProjectSiteClosed(FSharpCommonConstants.FSharpLanguageServiceCallbackName, AdviseProjectSiteChanges(fun () -> project.Disconnect())) + site.AdviseProjectSiteClosed(FSharpCommonConstants.FSharpLanguageServiceCallbackName, AdviseProjectSiteChanges(fun () -> ClearOptions(project.Id); project.Disconnect())) | _ -> () member this.SetupStandAloneFile(fileName: string, fileContents: string, workspace: VisualStudioWorkspaceImpl, hier: IVsHierarchy) = - let options = FSharpLanguageService.Checker.GetProjectOptionsFromScript(fileName, fileContents, DateTime.Now, [| |]) |> Async.RunSynchronously - let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(options.ProjectFileName, options.ProjectFileName) - FSharpLanguageService.UpdateOptions(projectId, options) + let loadTime = DateTime.Now + let options = ComputeSingleFileOptions (fileName, loadTime, fileContents, workspace) |> Async.RunSynchronously + + let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(options.ProjectFileName, options.ProjectFileName) + singleFileProjectTable.[projectId] <- loadTime if obj.ReferenceEquals(workspace.ProjectTracker.GetProject(projectId), null) then let projectContextFactory = this.Package.ComponentModel.GetService(); @@ -150,7 +220,9 @@ type internal FSharpLanguageService(package : FSharpPackage) = let project = projectContext :?> AbstractProject let document = project.GetCurrentDocumentFromPath(fileName) - document.Closing.Add(fun _ -> project.Disconnect()) + document.Closing.Add(fun _ -> + singleFileProjectTable.Remove(projectId) |> ignore; + project.Disconnect()) and [] diff --git a/vsintegration/src/FSharp.Editor/ProjectDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/ProjectDiagnosticAnalyzer.fs index 8fe4f65157e1..ff6ca2d40bcb 100644 --- a/vsintegration/src/FSharp.Editor/ProjectDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/ProjectDiagnosticAnalyzer.fs @@ -47,7 +47,7 @@ type internal FSharpProjectDiagnosticAnalyzer() = override this.AnalyzeProjectAsync(project: Project, cancellationToken: CancellationToken): Task> = async { - match FSharpLanguageService.GetOptions(project.Id) with + match FSharpLanguageService.GetOptionsForProject(project.Id) with | Some(options) -> return! FSharpProjectDiagnosticAnalyzer.GetDiagnostics(options) | None -> return ImmutableArray.Empty } |> CommonRoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs index 6f061d08432e..20de84ff992e 100644 --- a/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs +++ b/vsintegration/src/FSharp.Editor/QuickInfoProvider.fs @@ -92,7 +92,8 @@ type internal FSharpQuickInfoProvider [ = async { - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let defines = CompilerEnvironment.GetCompilationDefinesForEditing(document.Name, options.OtherOptions |> Seq.toList) diff --git a/vsintegration/src/FSharp.Editor/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/SignatureHelp.fs index 74d1fe3bb928..26fbae283828 100644 --- a/vsintegration/src/FSharp.Editor/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/SignatureHelp.fs @@ -188,7 +188,8 @@ type FSharpSignatureHelpProvider [] (serviceProvider: SVs member this.GetItemsAsync(document, position, triggerInfo, cancellationToken) = async { try - match FSharpLanguageService.GetOptions(document.Project.Id) with + let! optionsOpt = FSharpLanguageService.GetOptionsForDocumentOrProject(document) + match optionsOpt with | Some(options) -> let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let! textVersion = document.GetTextVersionAsync(cancellationToken) |> Async.AwaitTask diff --git a/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs b/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs index 82dc1cb1d37e..2efc61fc17af 100644 --- a/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs +++ b/vsintegration/src/FSharp.LanguageService/BackgroundRequests.fs @@ -84,7 +84,7 @@ type internal FSharpLanguageServiceBackgroundRequests // This portion is executed on the UI thread. let rdt = getServiceProvider().RunningDocumentTable let projectSite = getProjectSitesAndFiles().FindOwningProject(rdt,fileName) - let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, fileName) + let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, fileName, None) let projectFileName = projectSite.ProjectFileName() let data = { ProjectSite = projectSite diff --git a/vsintegration/src/FSharp.LanguageService/FSharpSource.fs b/vsintegration/src/FSharp.LanguageService/FSharpSource.fs index a8fcea047f66..04957099d16d 100644 --- a/vsintegration/src/FSharp.LanguageService/FSharpSource.fs +++ b/vsintegration/src/FSharp.LanguageService/FSharpSource.fs @@ -328,7 +328,8 @@ type internal FSharpSource(service:LanguageService, textLines, colorizer, vsFile IsIncompleteTypeCheckEnvironment = true UseScriptResolutionRules = false LoadTime = new System.DateTime(2000,1,1) // dummy data, just enough to get a parse - UnresolvedReferences = None } + UnresolvedReferences = None + ExtraProjectInfo=None } ic.ParseFileInProject(fileName, source.GetText(), co) |> Async.RunSynchronously diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index 99ca0951f930..079bfd9e3857 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -161,7 +161,7 @@ type internal ProjectSitesAndFiles() = | None -> ProjectSitesAndFiles.ProjectSiteOfSingleFile(filename) /// Create project options for this project site. - static member GetProjectOptionsForProjectSite(projectSite:IProjectSite,filename)= + static member GetProjectOptionsForProjectSite(projectSite:IProjectSite,filename,extraProjectInfo) = match projectSite with | :? IHaveCheckOptions as hco -> hco.OriginalCheckOptions() @@ -173,7 +173,8 @@ type internal ProjectSitesAndFiles() = IsIncompleteTypeCheckEnvironment = projectSite.IsIncompleteTypeCheckEnvironment UseScriptResolutionRules = SourceFile.MustBeSingleFileProject(filename) LoadTime = projectSite.LoadTime - UnresolvedReferences = None } + UnresolvedReferences = None + ExtraProjectInfo=extraProjectInfo } /// Create project site for these project options static member CreateProjectSiteForScript (filename, checkOptions) = ProjectSiteOfScriptFile (filename, checkOptions) :> IProjectSite diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index 42d97060e916..c0e00f898976 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -69,7 +69,7 @@ type internal FSharpLanguageServiceTestable() as this = if this.Unhooked then raise Error.UseOfUnhookedLanguageServiceState artifacts <- Some (ProjectSitesAndFiles()) let checker = FSharpChecker.Create() - checker.BeforeBackgroundFileCheck.Add (fun filename -> UIThread.Run(fun () -> this.NotifyFileTypeCheckStateIsDirty(filename))) + checker.BeforeBackgroundFileCheck.Add (fun (filename,_) -> UIThread.Run(fun () -> this.NotifyFileTypeCheckStateIsDirty(filename))) checkerContainerOpt <- Some (checker) serviceProvider <- Some sp isInitialized <- true @@ -126,7 +126,7 @@ type internal FSharpLanguageServiceTestable() as this = /// Respond to project being cleaned/rebuilt (any live type providers in the project should be refreshed) member this.OnProjectCleaned(projectSite:IProjectSite) = - let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, "") + let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, "",None) this.FSharpChecker.NotifyProjectCleaned(checkOptions) member this.OnActiveViewChanged(textView) = @@ -168,7 +168,7 @@ type internal FSharpLanguageServiceTestable() as this = interface IDependencyFileChangeNotify with member this.DependencyFileCreated projectSite = // Invalidate the configuration if we notice any add for any DependencyFiles - let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, "") + let checkOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(projectSite, "", None) this.FSharpChecker.InvalidateConfiguration(checkOptions) member this.DependencyFileChanged (filename) =