@@ -20,32 +20,28 @@ open Microsoft.VisualStudio.LanguageServices.ProjectSystem
2020open Microsoft.VisualStudio .Shell .Interop
2121
2222[<Sealed>]
23- type internal LegacyProjectWorkspaceMap ( workspace : VisualStudioWorkspaceImpl ,
24- solution: IVsSolution,
23+ type internal LegacyProjectWorkspaceMap ( solution : IVsSolution ,
2524 projectInfoManager: FSharpProjectOptionsManager,
26- projectContextFactory: IWorkspaceProjectContextFactory,
27- serviceProvider: IServiceProvider) as this=
25+ projectContextFactory: IWorkspaceProjectContextFactory) as this=
2826
2927let invalidPathChars = set( Path.GetInvalidPathChars())
3028let optionsAssociation = ConditionalWeakTable< IWorkspaceProjectContext, string[]>()
3129let isPathWellFormed ( path : string ) = not ( String.IsNullOrWhiteSpace path) && path|> Seq.forall( fun c -> not ( Set.contains c invalidPathChars))
3230
31+ let legacyProjectIdLookup = ConcurrentDictionary()
3332let legacyProjectLookup = ConcurrentDictionary()
33+ let setupQueue = ConcurrentQueue()
3434
35- let tryGetOrCreateProjectId ( workspace : VisualStudioWorkspaceImpl ) ( projectFileName : string ) =
36- let projectDisplayName = projectDisplayNameOf projectFileName
37- Some( workspace.ProjectTracker.GetOrCreateProjectIdForPath( projectFileName, projectDisplayName))
38-
39- member this.Initialize () =
35+ do
4036 solution.AdviseSolutionEvents( this) |> ignore
4137
4238/// Sync the Roslyn information for the project held in 'projectContext' to match the information given by 'site'.
4339/// Also sync the info in ProjectInfoManager if necessary.
44- member this.SyncLegacyProject ( projectId : ProjectId , projectContext : IWorkspaceProjectContext , site : IProjectSite , workspace : VisualStudioWorkspaceImpl , forceUpdate , userOpName ) =
40+ member this.SyncLegacyProject ( projectContext : IWorkspaceProjectContext , site : IProjectSite ) =
4541let wellFormedFilePathSetIgnoreCase ( paths : seq < string >) =
4642 HashSet( paths|> Seq.filter isPathWellFormed|> Seq.map( fun s -> try Path.GetFullPath( s) with _ -> s), StringComparer.OrdinalIgnoreCase)
4743
48- letmutable updated = forceUpdate
44+ let projectId = projectContext.Id
4945
5046// Sync the source files in projectContext. Note that these source files are __not__ maintained in order in projectContext
5147// as edits are made. It seems this is ok because the source file list is only used to drive roslyn per-file checking.
@@ -58,12 +54,10 @@ type internal LegacyProjectWorkspaceMap(workspace: VisualStudioWorkspaceImpl,
5854for filein updatedFilesdo
5955if not ( originalFiles.Contains( file)) then
6056 projectContext.AddSourceFile( file)
61- updated<- true
6257
6358for filein originalFilesdo
6459if not ( updatedFiles.Contains( file)) then
6560 projectContext.RemoveSourceFile( file)
66- updated<- true
6761
6862let updatedRefs = site.CompilationReferences|> wellFormedFilePathSetIgnoreCase
6963let originalRefs =
@@ -74,12 +68,10 @@ type internal LegacyProjectWorkspaceMap(workspace: VisualStudioWorkspaceImpl,
7468for refin updatedRefsdo
7569if not ( originalRefs.Contains( ref)) then
7670 projectContext.AddMetadataReference( ref, MetadataReferenceProperties.Assembly)
77- updated<- true
7871
7972for refin originalRefsdo
8073if not ( updatedRefs.Contains( ref)) then
8174 projectContext.RemoveMetadataReference( ref)
82- updated<- true
8375
8476// Update the project options association
8577let ok , originalOptions = optionsAssociation.TryGetValue( projectContext)
@@ -99,94 +91,93 @@ type internal LegacyProjectWorkspaceMap(workspace: VisualStudioWorkspaceImpl,
9991if okthen optionsAssociation.Remove( projectContext) |> ignore
10092 optionsAssociation.Add( projectContext, updatedOptions)
10193
102- updated<- true
103-
104- // update the cached options
105- if updatedthen
106- projectInfoManager.UpdateProjectInfo( tryGetOrCreateProjectId workspace, projectId, site, userOpName+ " .SyncLegacyProject" , invalidateConfig= true )
94+ projectContext.BinOutputPath<- Option.toObj site.CompilationBinOutputPath
10795
10896let info = ( updatedFiles, updatedRefs)
10997 legacyProjectLookup.AddOrUpdate( projectId, info, fun _ _ -> info) |> ignore
11098
111- member this.SetupLegacyProjectFile ( siteProvider : IProvideProjectSite , workspace : VisualStudioWorkspaceImpl , userOpName ) =
112- let userOpName = userOpName+ " .SetupProjectFile"
113- let rec setup ( site : IProjectSite ) =
99+ member this.SetupLegacyProjectFile ( siteProvider : IProvideProjectSite ) =
100+ let rec setup ( site : IProjectSite ) =
114101let projectGuid = Guid( site.ProjectGuid)
115102let projectFileName = site.ProjectFileName
116103let projectDisplayName = projectDisplayNameOf projectFileName
117104
118- // This projectId is not guaranteed to be the same ProjectId that will actually be created once we call CreateProjectContext
119- // in Roslyn versions once https://github.com/dotnet/roslyn/pull/26931 is merged. Roslyn will still guarantee that once
120- // there is a project in the workspace with the same path, it'll return the ID of that. So this is sufficient to use
121- // in that case as long as we only use it to call GetProject.
122- let fakeProjectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath( projectFileName, projectDisplayName)
123-
124- if isNull( workspace.ProjectTracker.GetProject fakeProjectId) then
125- let hierarchy =
126- site.ProjectProvider
127- |> Option.map( fun p -> p:?> IVsHierarchy)
128- |> Option.toObj
129-
130- // Roslyn is expecting site to be an IVsHierarchy.
131- // It just so happens that the object that implements IProvideProjectSite is also
132- // an IVsHierarchy. This assertion is to ensure that the assumption holds true.
133- Debug.Assert( not ( isNull hierarchy), " About to CreateProjectContext with a non-hierarchy site" )
134-
135- let projectContext =
136- projectContextFactory.CreateProjectContext(
137- FSharpConstants.FSharpLanguageName,
138- projectDisplayName,
139- projectFileName,
140- projectGuid,
141- hierarchy,
142- Option.toObj site.CompilationBinOutputPath)
143-
144- // The real project ID that was actually added. See comments for fakeProjectId why this one is actually good.
145- let realProjectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath( projectFileName, projectDisplayName)
146-
147- // Sync IProjectSite --> projectContext, and IProjectSite --> ProjectInfoManage
148- this.SyncLegacyProject( realProjectId, projectContext, site, workspace, forceUpdate= true , userOpName= userOpName)
149-
150- site.BuildErrorReporter<- Some( projectContext:?> Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2)
151-
152- // TODO: consider forceUpdate = false here. forceUpdate=true may be causing repeated computation?
153- site.AdviseProjectSiteChanges( FSharpConstants.FSharpLanguageServiceCallbackName,
154- AdviseProjectSiteChanges( fun () -> this.SyncLegacyProject( realProjectId, projectContext, site, workspace, forceUpdate= true , userOpName= " AdviseProjectSiteChanges." + userOpName)))
155-
156- site.AdviseProjectSiteClosed( FSharpConstants.FSharpLanguageServiceCallbackName,
157- AdviseProjectSiteChanges( fun () ->
158- projectInfoManager.ClearInfoForProject( realProjectId)
159- optionsAssociation.Remove( projectContext) |> ignore
160- projectContext.Dispose()))
161-
162- for referencedSitein ProjectSitesAndFiles.GetReferencedProjectSites( Some realProjectId, site, serviceProvider, Some( workspace:> obj), Some projectInfoManager.FSharpOptions) do
163- setup referencedSite
105+ let hierarchy =
106+ site.ProjectProvider
107+ |> Option.map( fun p -> p:?> IVsHierarchy)
108+ |> Option.toObj
109+
110+ // Roslyn is expecting site to be an IVsHierarchy.
111+ // It just so happens that the object that implements IProvideProjectSite is also
112+ // an IVsHierarchy. This assertion is to ensure that the assumption holds true.
113+ Debug.Assert( not ( isNull hierarchy), " About to CreateProjectContext with a non-hierarchy site" )
114+
115+ let projectContext =
116+ projectContextFactory.CreateProjectContext(
117+ FSharpConstants.FSharpLanguageName,
118+ projectDisplayName,
119+ projectFileName,
120+ projectGuid,
121+ hierarchy,
122+ Option.toObj site.CompilationBinOutputPath)
123+
124+ legacyProjectIdLookup.[ projectGuid] <- projectContext.Id
125+
126+ // Sync IProjectSite --> projectContext, and IProjectSite --> ProjectInfoManage
127+ this.SyncLegacyProject( projectContext, site)
128+
129+ site.BuildErrorReporter<- Some( projectContext:?> Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2)
130+
131+ // TODO: consider forceUpdate = false here. forceUpdate=true may be causing repeated computation?
132+ site.AdviseProjectSiteChanges( FSharpConstants.FSharpLanguageServiceCallbackName,
133+ AdviseProjectSiteChanges( fun () -> this.SyncLegacyProject( projectContext, site)))
134+
135+ site.AdviseProjectSiteClosed( FSharpConstants.FSharpLanguageServiceCallbackName,
136+ AdviseProjectSiteChanges( fun () ->
137+ projectInfoManager.ClearInfoForProject( projectContext.Id)
138+ optionsAssociation.Remove( projectContext) |> ignore
139+ projectContext.Dispose()))
164140
165141 setup( siteProvider.GetProjectSite())
166142
167143interface IVsSolutionEventswith
168144
169- member __.OnAfterCloseSolution ( _ ) = VSConstants.S_ OK
145+ member __.OnAfterCloseSolution ( _ ) =
146+ // Clear
147+ let mutable setup = Unchecked.defaultof<_>
148+ while setupQueue.TryDequeue(& setup) do ()
149+ VSConstants.S_ OK
170150
171151member __.OnAfterLoadProject ( _ , _ ) = VSConstants.S_ OK
172152
173153member __.OnAfterOpenProject ( hier , _ ) =
174154match hierwith
175155| :? IProvideProjectSiteas siteProvider->
176- this.SetupLegacyProjectFile( siteProvider, workspace, " LegacyProjectWorkspaceMap.Initialize" )
156+ let setup = fun () -> this.SetupLegacyProjectFile( siteProvider)
157+ let _ , o = solution.GetProperty( int__ VSPROPID.VSPROPID_ IsSolutionOpen)
158+ if ( match owith | :? boolas isOpen-> isOpen| _ -> false ) then
159+ setup()
160+ else
161+ setupQueue.Enqueue( setup)
177162| _ -> ()
178163 VSConstants.S_ OK
179164
180- member __.OnAfterOpenSolution ( _ , _ ) = VSConstants.S_ OK
165+ member __.OnAfterOpenSolution ( _ , _ ) =
166+ let mutable setup = Unchecked.defaultof<_>
167+ while setupQueue.TryDequeue(& setup) do
168+ setup()
169+ VSConstants.S_ OK
181170
182171member __.OnBeforeCloseProject ( hier , _ ) =
183172match hierwith
184173| :? IProvideProjectSiteas siteProvider->
185174let site = siteProvider.GetProjectSite()
186- let projectFileName = site.ProjectFileName
187- let projectDisplayName = projectDisplayNameOf projectFileName
188- let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath( projectFileName, projectDisplayName)
189- legacyProjectLookup.TryRemove( projectId) |> ignore
175+ let projectGuid = Guid( site.ProjectGuid)
176+ match legacyProjectIdLookup.TryGetValue( projectGuid) with
177+ | true , projectId->
178+ legacyProjectIdLookup.TryRemove( projectGuid) |> ignore
179+ legacyProjectLookup.TryRemove( projectId) |> ignore
180+ | _ -> ()
190181| _ -> ()
191182 VSConstants.S_ OK
192183