@@ -14,7 +14,6 @@ open Microsoft.FSharp.Compiler.CompileOps
1414open Microsoft.FSharp .Compiler .SourceCodeServices
1515open Microsoft.VisualStudio
1616open Microsoft.VisualStudio .FSharp .Editor
17- open Microsoft.VisualStudio .FSharp .Editor .SiteProvider
1817open Microsoft.VisualStudio .LanguageServices
1918open Microsoft.VisualStudio .LanguageServices .Implementation .ProjectSystem
2019open Microsoft.VisualStudio .Shell
@@ -34,8 +33,6 @@ module private FSharpProjectOptionsHelpers =
3433{
3534new IProvideProjectSitewith
3635member x.GetProjectSite () =
37- let fst ( a , _ , _ ) = a
38- let snd ( _ , b , _ ) = b
3936let mutable errorReporter =
4037let reporter = ProjectExternalErrorReporter( project.Id, " FS" , serviceProvider)
4138 Some( reporter:> IVsLanguageServiceBuildErrorReporter2 )
@@ -83,8 +80,10 @@ module private FSharpProjectOptionsHelpers =
8380
8481[<RequireQualifiedAccess>]
8582type private FSharpProjectOptionsMessage =
86- | TryGetOptionsof Project * AsyncReplyChannel <( FSharpParsingOptions * FSharpProjectOptions ) option >
83+ | TryGetOptionsByDocumentof Document * AsyncReplyChannel <( FSharpParsingOptions * FSharpProjectOptions ) option >
84+ | TryGetOptionsByProjectof Project * AsyncReplyChannel <( FSharpParsingOptions * FSharpProjectOptions ) option >
8785| ClearOptionsof ProjectId
86+ | ClearSingleFileOptionsCacheof DocumentId
8887
8988[<Sealed>]
9089type private FSharpProjectOptionsReactor ( workspace : VisualStudioWorkspaceImpl , settings : EditorOptions , serviceProvider , checkerProvider : FSharpCheckerProvider ) =
@@ -94,6 +93,49 @@ type private FSharpProjectOptionsReactor (workspace: VisualStudioWorkspaceImpl,
9493let cpsCommandLineOptions = new ConcurrentDictionary< ProjectId, string[] * string[]>()
9594
9695let cache = Dictionary< ProjectId, VersionStamp* FSharpParsingOptions* FSharpProjectOptions>()
96+ let singleFileCache = Dictionary< DocumentId, VersionStamp* FSharpParsingOptions* FSharpProjectOptions>()
97+
98+ let rec tryComputeOptionsByFile ( document : Document ) =
99+ async {
100+ let! text = document.GetTextAsync() |> Async.AwaitTask
101+ let! fileStamp = document.GetTextVersionAsync() |> Async.AwaitTask
102+ let! scriptProjectOptions , _ = checkerProvider.Checker.GetProjectOptionsFromScript( document.FilePath, text.ToString(), DateTime.Now)
103+ match singleFileCache.TryGetValue( document.Id) with
104+ | false , _ ->
105+ let projectOptions =
106+ if isScriptFile document.FilePaththen
107+ scriptProjectOptions
108+ else
109+ {
110+ ProjectFileName= document.FilePath
111+ ProjectId= None
112+ SourceFiles= [| document.FilePath|]
113+ OtherOptions= [||]
114+ ReferencedProjects= [||]
115+ IsIncompleteTypeCheckEnvironment= false
116+ UseScriptResolutionRules= SourceFile.MustBeSingleFileProject( Path.GetFileName( document.FilePath))
117+ LoadTime= DateTime.Now
118+ UnresolvedReferences= None
119+ OriginalLoadReferences= []
120+ ExtraProjectInfo= None
121+ Stamp= Some( int64( fileStamp.GetHashCode()))
122+ }
123+
124+ checkerProvider.Checker.InvalidateConfiguration( projectOptions, startBackgroundCompileIfAlreadySeen= true , userOpName= " computeOptions" )
125+
126+ let parsingOptions , _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions( projectOptions)
127+
128+ singleFileCache.[ document.Id] <- ( fileStamp, parsingOptions, projectOptions)
129+
130+ return Some( parsingOptions, projectOptions)
131+
132+ | true , ( fileStamp2, parsingOptions, projectOptions) ->
133+ if fileStamp<> fileStamp2then
134+ singleFileCache.Remove( document.Id) |> ignore
135+ return ! tryComputeOptionsByFile document
136+ else
137+ return Some( parsingOptions, projectOptions)
138+ }
97139
98140let rec tryComputeOptions ( project : Project ) =
99141let projectId = project.Id
@@ -200,24 +242,48 @@ type private FSharpProjectOptionsReactor (workspace: VisualStudioWorkspaceImpl,
200242async {
201243while true do
202244match ! agent.Receive() with
203- | FSharpProjectOptionsMessage.TryGetOptions( project, reply) ->
245+ | FSharpProjectOptionsMessage.TryGetOptionsByDocument( document, reply) ->
246+ try
247+ // For now, disallow miscellaneous workspace since we are using the hacky F# miscellaneous files project.
248+ if document.Project.Solution.Workspace.Kind= WorkspaceKind.MiscellaneousFilesthen
249+ reply.Reply( None)
250+ elif document.Project.Name= FSharpConstants.FSharpMiscellaneousFilesNamethen
251+ let! options = tryComputeOptionsByFile document
252+ reply.Reply( options)
253+ else
254+ reply.Reply( tryComputeOptions document.Project)
255+ with
256+ | _ ->
257+ reply.Reply( None)
258+ | FSharpProjectOptionsMessage.TryGetOptionsByProject( project, reply) ->
204259try
205- reply.Reply( tryComputeOptions project)
260+ if project.Solution.Workspace.Kind= WorkspaceKind.MiscellaneousFiles|| project.Name= FSharpConstants.FSharpMiscellaneousFilesNamethen
261+ reply.Reply( None)
262+ else
263+ reply.Reply( tryComputeOptions project)
206264with
207265| _ ->
208266 reply.Reply( None)
209267| FSharpProjectOptionsMessage.ClearOptions( projectId) ->
210268 cache.Remove( projectId) |> ignore
269+ | FSharpProjectOptionsMessage.ClearSingleFileOptionsCache( documentId) ->
270+ singleFileCache.Remove( documentId) |> ignore
211271}
212272
213273let agent = MailboxProcessor.Start(( fun agent -> loop agent), cancellationToken= cancellationTokenSource.Token)
214274
215275member __.TryGetOptionsByProjectAsync ( project ) =
216- agent.PostAndAsyncReply( fun reply -> FSharpProjectOptionsMessage.TryGetOptions( project, reply))
276+ agent.PostAndAsyncReply( fun reply -> FSharpProjectOptionsMessage.TryGetOptionsByProject( project, reply))
277+
278+ member __.TryGetOptionsByDocumentAsync ( document ) =
279+ agent.PostAndAsyncReply( fun reply -> FSharpProjectOptionsMessage.TryGetOptionsByDocument( document, reply))
217280
218281member __.ClearOptionsByProjectId ( projectId ) =
219282 agent.Post( FSharpProjectOptionsMessage.ClearOptions( projectId))
220283
284+ member __.ClearSingleFileOptionsCache ( documentId ) =
285+ agent.Post( FSharpProjectOptionsMessage.ClearSingleFileOptionsCache( documentId))
286+
221287member __.SetCpsCommandLineOptions ( projectId , sourcePaths , options ) =
222288 cpsCommandLineOptions.[ projectId] <- ( sourcePaths, options)
223289
@@ -247,11 +313,11 @@ type internal FSharpProjectOptionsManager
247313 settings: EditorOptions
248314) =
249315
250- let reactor = new FSharpProjectOptionsReactor( workspace, settings, serviceProvider, checkerProvider)
316+ let projectDisplayNameOf projectFileName =
317+ if String.IsNullOrWhiteSpace projectFileNamethen projectFileName
318+ else Path.GetFileNameWithoutExtension projectFileName
251319
252- // A table of information about single-file projects. Currently we only need the load time of each such file, plus
253- // the original options for editing
254- let singleFileProjectTable = ConcurrentDictionary< ProjectId, DateTime* FSharpParsingOptions* FSharpProjectOptions>()
320+ let reactor = new FSharpProjectOptionsReactor( workspace, settings, serviceProvider, checkerProvider)
255321
256322do
257323// We need to listen to this event for lifecycle purposes.
@@ -265,35 +331,8 @@ type internal FSharpProjectOptionsManager
265331member this.ClearInfoForProject ( projectId : ProjectId ) =
266332 reactor.ClearOptionsByProjectId( projectId)
267333
268- /// Clear a project from the single file project table
269- member this.ClearInfoForSingleFileProject ( projectId ) =
270- singleFileProjectTable.TryRemove( projectId) |> ignore
271-
272- /// Update a project in the single file project table
273- member this.AddOrUpdateSingleFileProject ( projectId , data ) = singleFileProjectTable.[ projectId] <- data
274-
275- /// Get the exact options for a single-file script
276- member this.ComputeSingleFileOptions ( tryGetOrCreateProjectId , fileName , loadTime , fileContents , solution ) =
277- async {
278- let extraProjectInfo = Some( box workspace)
279- if SourceFile.MustBeSingleFileProject( fileName) then
280- // NOTE: we don't use a unique stamp for single files, instead comparing options structurally.
281- // This is because we repeatedly recompute the options.
282- let optionsStamp = None
283- let! options , _diagnostics = checkerProvider.Checker.GetProjectOptionsFromScript( fileName, fileContents, loadTime, [| |], ?extraProjectInfo= extraProjectInfo, ?optionsStamp= optionsStamp)
284- // NOTE: we don't use FCS cross-project references from scripts to projects. THe projects must have been
285- // compiled and #r will refer to files on disk
286- let referencedProjectFileNames = [| |]
287- let site = ProjectSitesAndFiles.CreateProjectSiteForScript( fileName, referencedProjectFileNames, options)
288- let deps , projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite( settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, site, serviceProvider, ( tryGetOrCreateProjectId fileName), fileName, options.ExtraProjectInfo, solution, None)
289- let parsingOptions , _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions( projectOptions)
290- return ( deps, parsingOptions, projectOptions)
291- else
292- let site = ProjectSitesAndFiles.ProjectSiteOfSingleFile( fileName)
293- let deps , projectOptions = ProjectSitesAndFiles.GetProjectOptionsForProjectSite( settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, site, serviceProvider, ( tryGetOrCreateProjectId fileName), fileName, extraProjectInfo, solution, None)
294- let parsingOptions , _ = checkerProvider.Checker.GetParsingOptionsFromProjectOptions( projectOptions)
295- return ( deps, parsingOptions, projectOptions)
296- }
334+ member this.ClearSingleFileOptionsCache ( documentId ) =
335+ reactor.ClearSingleFileOptionsCache( documentId)
297336
298337/// Get compilation defines relevant for syntax processing.
299338/// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project
@@ -311,45 +350,20 @@ type internal FSharpProjectOptionsManager
311350/// Get the exact options for a document or project
312351member this.TryGetOptionsForDocumentOrProject ( document : Document ) =
313352async {
314- let projectId = document.Project.Id
315-
316- // The options for a single-file script project are re-requested each time the file is analyzed. This is because the
317- // single-file project may contain #load and #r references which are changing as the user edits, and we may need to re-analyze
318- // to determine the latest settings. FCS keeps a cache to help ensure these are up-to-date.
319- match singleFileProjectTable.TryGetValue( projectId) with
320- | true , ( loadTime, _, _) ->
321- try
322- let fileName = document.FilePath
323- let! cancellationToken = Async.CancellationToken
324- let! sourceText = document.GetTextAsync( cancellationToken) |> Async.AwaitTask
325- // NOTE: we don't use FCS cross-project references from scripts to projects. The projects must have been
326- // compiled and #r will refer to files on disk.
327- let tryGetOrCreateProjectId _ = None
328- let! _referencedProjectFileNames , parsingOptions , projectOptions = this.ComputeSingleFileOptions( tryGetOrCreateProjectId, fileName, loadTime, sourceText.ToString(), document.Project.Solution)
329- this.AddOrUpdateSingleFileProject( projectId, ( loadTime, parsingOptions, projectOptions))
330- return Some( parsingOptions, None, projectOptions)
331- with ex->
332- Assert.Exception( ex)
333- return None
353+ match ! reactor.TryGetOptionsByDocumentAsync( document) with
354+ | Some( parsingOptions, projectOptions) ->
355+ return Some( parsingOptions, None, projectOptions)
334356| _ ->
335- match ! reactor.TryGetOptionsByProjectAsync( document.Project) with
336- | Some( parsingOptions, projectOptions) ->
337- return Some( parsingOptions, None, projectOptions)
338- | _ ->
339- return None
357+ return None
340358}
341359
342360/// Get the options for a document or project relevant for syntax processing.
343361/// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project options for a script.
344362member this.TryGetOptionsForEditingDocumentOrProject ( document : Document ) =
345- let projectId = document.Project.Id
346- match singleFileProjectTable.TryGetValue( projectId) with
347- | true , (_ loadTime, parsingOptions, originalOptions) -> async { return Some( parsingOptions, originalOptions) }
348- | _ ->
349- async {
350- let! result = this.TryGetOptionsForDocumentOrProject( document)
351- return result|> Option.map( fun ( parsingOptions , _ , projectOptions ) -> parsingOptions, projectOptions)
352- }
363+ async {
364+ let! result = this.TryGetOptionsForDocumentOrProject( document)
365+ return result|> Option.map( fun ( parsingOptions , _ , projectOptions ) -> parsingOptions, projectOptions)
366+ }
353367
354368[<Export>]
355369/// This handles commandline change notifications from the Dotnet Project-system