33namespace Microsoft.VisualStudio.FSharp.Editor
44
55open System
6+ open System.Collections .Generic
67open System.Collections .Concurrent
78open System.Collections .Immutable
89open System.ComponentModel .Composition
@@ -17,6 +18,170 @@ open Microsoft.VisualStudio.FSharp.Editor.SiteProvider
1718open Microsoft.VisualStudio .LanguageServices
1819open Microsoft.VisualStudio .LanguageServices .Implementation .ProjectSystem
1920open Microsoft.VisualStudio .Shell
21+ open System.Threading
22+ open Microsoft.VisualStudio .Shell .Interop
23+ open Microsoft.VisualStudio .LanguageServices .Implementation .TaskList
24+
25+ [<AutoOpen>]
26+ module private FSharpProjectOptionsHelpers =
27+
28+ let mapProjectToSite ( workspace : VisualStudioWorkspaceImpl , project : Project , serviceProvider : System.IServiceProvider , projectOptionsTable : FSharpProjectOptionsTable option ) =
29+ let hier = workspace.GetHierarchy( project.Id)
30+ let getCommandLineOptionsWithProjectId ( projectId ) =
31+ match projectOptionsTablewith
32+ | Some( options) -> options.GetCommandLineOptionsWithProjectId( projectId)
33+ | None-> [||], [||], [||]
34+ {
35+ new IProvideProjectSitewith
36+ member x.GetProjectSite () =
37+ let fst ( a , _ , _ ) = a
38+ let snd ( _ , b , _ ) = b
39+ let mutable errorReporter =
40+ let reporter = ProjectExternalErrorReporter( project.Id, " FS" , serviceProvider)
41+ Some( reporter:> IVsLanguageServiceBuildErrorReporter2 )
42+
43+ {
44+ new IProjectSitewith
45+ member __.Description = project.Name
46+ member __.CompilationSourceFiles = getCommandLineOptionsWithProjectId( project.Id) |> fst
47+ member __.CompilationOptions =
48+ let _ , references , options = getCommandLineOptionsWithProjectId( project.Id)
49+ Array.concat[ options; references|> Array.map( fun r -> " -r:" + r)]
50+ member __.CompilationReferences = getCommandLineOptionsWithProjectId( project.Id) |> snd
51+ member site.CompilationBinOutputPath = site.CompilationOptions|> Array.tryPick( fun s -> if s.StartsWith( " -o:" ) then Some s.[ 3 ..] else None)
52+ member __.ProjectFileName = project.FilePath
53+ member __.AdviseProjectSiteChanges ( _ , _ ) = ()
54+ member __.AdviseProjectSiteCleaned ( _ , _ ) = ()
55+ member __.AdviseProjectSiteClosed ( _ , _ ) = ()
56+ member __.IsIncompleteTypeCheckEnvironment = false
57+ member __.TargetFrameworkMoniker = " "
58+ member __.ProjectGuid = project.Id.Id.ToString()
59+ member __.LoadTime = System.DateTime.Now
60+ member __.ProjectProvider = Some( x)
61+ member __.BuildErrorReporter with get() = errorReporterand set ( v ) = errorReporter<- v
62+ }
63+ interface IVsHierarchywith
64+ member __.SetSite ( psp ) = hier.SetSite( psp)
65+ member __.GetSite ( psp ) = hier.GetSite( ref psp)
66+ member __.QueryClose ( pfCanClose )= hier.QueryClose( ref pfCanClose)
67+ member __.Close () = hier.Close()
68+ member __.GetGuidProperty ( itemid , propid , pguid ) = hier.GetGuidProperty( itemid, propid, ref pguid)
69+ member __.SetGuidProperty ( itemid , propid , rguid ) = hier.SetGuidProperty( itemid, propid, ref rguid)
70+ member __.GetProperty ( itemid , propid , pvar ) = hier.GetProperty( itemid, propid, ref pvar)
71+ member __.SetProperty ( itemid , propid , var ) = hier.SetProperty( itemid, propid, var)
72+ member __.GetNestedHierarchy ( itemid , iidHierarchyNested , ppHierarchyNested , pitemidNested ) =
73+ hier.GetNestedHierarchy( itemid, ref iidHierarchyNested, ref ppHierarchyNested, ref pitemidNested)
74+ member __.GetCanonicalName ( itemid , pbstrName ) = hier.GetCanonicalName( itemid, ref pbstrName)
75+ member __.ParseCanonicalName ( pszName , pitemid ) = hier.ParseCanonicalName( pszName, ref pitemid)
76+ member __.Unused0 () = hier.Unused0()
77+ member __.AdviseHierarchyEvents ( pEventSink , pdwCookie ) = hier.AdviseHierarchyEvents( pEventSink, ref pdwCookie)
78+ member __.UnadviseHierarchyEvents ( dwCookie ) = hier.UnadviseHierarchyEvents( dwCookie)
79+ member __.Unused1 () = hier.Unused1()
80+ member __.Unused2 () = hier.Unused2()
81+ member __.Unused3 () = hier.Unused3()
82+ member __.Unused4 () = hier.Unused4()
83+ }
84+
85+ [<RequireQualifiedAccess>]
86+ type private FSharpProjectOptionsMessage =
87+ | TryGetOptionsof Project * AsyncReplyChannel <( FSharpParsingOptions * FSharpProjectOptions ) option >
88+ | ClearOptionsof ProjectId
89+
90+ [<Sealed>]
91+ type private FSharpProjectOptionsReactor ( workspace : VisualStudioWorkspaceImpl , settings : EditorOptions , optionsTable : FSharpProjectOptionsTable , serviceProvider , checkerProvider : FSharpCheckerProvider ) =
92+ let cancellationTokenSource = new CancellationTokenSource()
93+
94+ let cache = Dictionary< ProjectId, VersionStamp* FSharpParsingOptions* FSharpProjectOptions>()
95+
96+ let rec tryComputeOptions ( project : Project ) =
97+ let projectId = project.Id
98+ let projectStamp = project.Version
99+ match cache.TryGetValue( projectId) with
100+ | false , _ ->
101+
102+ let referencedProjects =
103+ if settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferencesthen
104+ project.ProjectReferences
105+ |> Seq.choose( fun projectReference ->
106+ let referenceProject = project.Solution.GetProject( projectReference.ProjectId)
107+ tryComputeOptions referenceProject
108+ |> Option.map( fun ( _ , projectOptions ) ->
109+ ( referenceProject.OutputFilePath, projectOptions)
110+ )
111+ )
112+ |> Seq.toArray
113+ else
114+ [||]
115+
116+ let hier = workspace.GetHierarchy( projectId)
117+ let projectSite =
118+ match hierwith
119+ | (:? IProvideProjectSiteas provideSite) -> provideSite.GetProjectSite()
120+ | _ ->
121+ let provideSite = mapProjectToSite( workspace, project, serviceProvider, Some( optionsTable))
122+ provideSite.GetProjectSite()
123+
124+ let projectOptions =
125+ {
126+ ProjectFileName= projectSite.ProjectFileName
127+ ProjectId= None
128+ SourceFiles= projectSite.CompilationSourceFiles
129+ OtherOptions= projectSite.CompilationOptions
130+ ReferencedProjects= referencedProjects
131+ IsIncompleteTypeCheckEnvironment= projectSite.IsIncompleteTypeCheckEnvironment
132+ UseScriptResolutionRules= SourceFile.MustBeSingleFileProject( Path.GetFileName( project.FilePath))
133+ LoadTime= projectSite.LoadTime
134+ UnresolvedReferences= None
135+ OriginalLoadReferences= []
136+ ExtraProjectInfo= None
137+ Stamp= Some( int64<| projectStamp.GetHashCode())
138+ }
139+
140+ if Array.isEmpty projectOptions.SourceFilesthen
141+ None
142+ else
143+
144+ checkerProvider.Checker.InvalidateConfiguration( projectOptions, startBackgroundCompileIfAlreadySeen= true , userOpName= " computeOptions" )
145+
146+ let parsingOptions , _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions( projectOptions)
147+
148+ cache.[ projectId] <- ( projectStamp, parsingOptions, projectOptions)
149+
150+ Some( parsingOptions, projectOptions)
151+
152+ | true , ( projectStamp2, parsingOptions, projectOptions) ->
153+ if projectStamp<> projectStamp2then
154+ cache.Remove( projectId) |> ignore
155+ tryComputeOptions project
156+ else
157+ Some( parsingOptions, projectOptions)
158+
159+ let loop ( agent : MailboxProcessor < FSharpProjectOptionsMessage >) =
160+ async {
161+ while true do
162+ try
163+ match ! agent.Receive() with
164+ | FSharpProjectOptionsMessage.TryGetOptions( project, reply) ->
165+ reply.Reply( tryComputeOptions project)
166+ | FSharpProjectOptionsMessage.ClearOptions( projectId) ->
167+ cache.Remove( projectId) |> ignore
168+ with
169+ | _ -> ()
170+ }
171+
172+ let agent = MailboxProcessor.Start(( fun agent -> loop agent), cancellationToken= cancellationTokenSource.Token)
173+
174+ member __.TryGetOptionsByProjectAsync ( project ) =
175+ agent.PostAndAsyncReply( fun reply -> FSharpProjectOptionsMessage.TryGetOptions( project, reply))
176+
177+ member __.ClearOptionsByProjectId ( projectId ) =
178+ agent.Post( FSharpProjectOptionsMessage.ClearOptions( projectId))
179+
180+ interface IDisposablewith
181+ member __.Dispose () =
182+ cancellationTokenSource.Cancel()
183+ cancellationTokenSource.Dispose()
184+ ( agent:> IDisposable) .Dispose()
20185
21186/// Exposes FCS FSharpProjectOptions information management as MEF component.
22187//
@@ -36,6 +201,8 @@ type internal FSharpProjectOptionsManager
36201// A table of information about projects, excluding single-file projects.
37202let projectOptionsTable = FSharpProjectOptionsTable()
38203
204+ let reactor = new FSharpProjectOptionsReactor( workspace, settings, projectOptionsTable, serviceProvider, checkerProvider)
205+
39206// A table of information about single-file projects. Currently we only need the load time of each such file, plus
40207// the original options for editing
41208let singleFileProjectTable = ConcurrentDictionary< ProjectId, DateTime* FSharpParsingOptions* FSharpProjectOptions>()
@@ -48,7 +215,9 @@ type internal FSharpProjectOptionsManager
48215member __.FSharpOptions = projectOptionsTable
49216
50217/// Clear a project from the project table
51- member this.ClearInfoForProject ( projectId : ProjectId ) = projectOptionsTable.ClearInfoForProject( projectId)
218+ member this.ClearInfoForProject ( projectId : ProjectId ) =
219+ projectOptionsTable.ClearInfoForProject( projectId)
220+ reactor.ClearOptionsByProjectId( projectId)
52221
53222/// Clear a project from the single file project table
54223member this.ClearInfoForSingleFileProject ( projectId ) =
@@ -129,31 +298,24 @@ type internal FSharpProjectOptionsManager
129298 Assert.Exception( ex)
130299return None
131300| _ ->
132- match this.TryGetOptionsForProject( projectId) with
133- | Some( parsingOptions, site, projectOptions) ->
134- let projectOptions =
135- { projectOptionswith
136- ReferencedProjects=
137- document.Project.ProjectReferences
138- |> Seq.choose( fun projectReference ->
139- let referenceProject = document.Project.Solution.GetProject( projectReference.ProjectId)
140- match this.TryGetOptionsForProject( projectReference.ProjectId) with
141- | Some(_, _, referenceProjectOptions) -> Some( referenceProject.OutputFilePath, referenceProjectOptions)
142- | _ -> None
143- )
144- |> Seq.toArray
145- }
146- return Some( parsingOptions, site, projectOptions)
147- | _ -> return None
301+ match ! reactor.TryGetOptionsByProjectAsync( document.Project) with
302+ | Some( parsingOptions, projectOptions) ->
303+ return Some( parsingOptions, None, projectOptions)
304+ | _ ->
305+ return None
148306}
149307
150308/// Get the options for a document or project relevant for syntax processing.
151309/// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project options for a script.
152310member this.TryGetOptionsForEditingDocumentOrProject ( document : Document ) =
153311let projectId = document.Project.Id
154312match singleFileProjectTable.TryGetValue( projectId) with
155- | true , (_ loadTime, parsingOptions, originalOptions) -> Some( parsingOptions, originalOptions)
156- | _ -> this.TryGetOptionsForProject( projectId) |> Option.map( fun ( parsingOptions , _ , projectOptions ) -> parsingOptions, projectOptions)
313+ | true , (_ loadTime, parsingOptions, originalOptions) -> async { return Some( parsingOptions, originalOptions) }
314+ | _ ->
315+ async {
316+ let! result = this.TryGetOptionsForDocumentOrProject( document)
317+ return result|> Option.map( fun ( parsingOptions , _ , projectOptions ) -> parsingOptions, projectOptions)
318+ }
157319
158320/// get a siteprovider
159321member this.ProvideProjectSiteProvider ( project : Project ) = provideProjectSiteProvider( workspace, project, serviceProvider, Some projectOptionsTable)
@@ -197,6 +359,5 @@ type internal FSharpProjectOptionsManager
197359let referencePaths = references|> Seq.map( fun r -> fullPath r.Reference) |> Seq.toArray
198360
199361 projectOptionsTable.SetOptionsWithProjectId( projectId, sourcePaths, referencePaths, options.ToArray())
200- this.UpdateProjectInfoWithProjectId( projectId, " HandleCommandLineChanges" , invalidateConfig= true , solution= workspace.CurrentSolution)
201362
202363member __.Checker = checkerProvider.Checker