Skip to content
This repository was archived by the owner on Dec 23, 2024. It is now read-only.

Commit d99a212

Browse files
authored
Another go at --- Map workspace to iprojectsite (dotnet#3564)
* Saved * SDK editor support * Address merge issues * Feedback * reduce delta * invalidate ide when a new file is discovered * Go faster stripes * feedback + refactor * tryFindIndex * Cleanup + Ensure that FSharp.Core.BuildFromSource.proj works at designtime * final clean up * label for HandleCommandLineChanges * ensure some buildfromsource porojects load
1 parent 34c3bcc commit d99a212

File tree

1 file changed

+139
-38
lines changed

1 file changed

+139
-38
lines changed

LanguageService/LanguageService.fs

Lines changed: 139 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
22

3-
namespace Microsoft.VisualStudio.FSharp.Editor
3+
namespace rec Microsoft.VisualStudio.FSharp.Editor
44

55
#nowarn "40"
66

77
open System
88
open System.Collections.Concurrent
99
open System.Collections.Generic
10+
open System.Collections.Immutable
1011
open System.ComponentModel.Composition
12+
open System.Diagnostics
13+
open System.IO
14+
open System.Linq
1115
open System.Runtime.CompilerServices
1216
open System.Runtime.InteropServices
13-
open System.IO
14-
open System.Diagnostics
17+
open System.Threading
1518

1619
open Microsoft.FSharp.Compiler.CompileOps
1720
open Microsoft.FSharp.Compiler.SourceCodeServices
21+
open Microsoft.FSharp.Compiler.AbstractIL.Internal.Library
1822

1923
open Microsoft.CodeAnalysis
2024
open Microsoft.CodeAnalysis.Diagnostics
@@ -23,6 +27,7 @@ open Microsoft.CodeAnalysis.Options
2327
open Microsoft.VisualStudio
2428
open Microsoft.VisualStudio.Editor
2529
open Microsoft.VisualStudio.TextManager.Interop
30+
open Microsoft.VisualStudio.LanguageServices
2631
open Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService
2732
open Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
2833
open Microsoft.VisualStudio.LanguageServices.Implementation.TaskList
@@ -78,7 +83,6 @@ type internal FSharpCheckerProvider
7883
member this.Checker = checker.Value
7984

8085

81-
8286
/// A value and a function to recompute/refresh the value. The function is passed a flag indicating if a refresh is happening.
8387
type Refreshable<'T> = 'T * (bool -> 'T)
8488

@@ -87,22 +91,33 @@ type Refreshable<'T> = 'T * (bool -> 'T)
8791
// This service allows analyzers to get an appropriate FSharpProjectOptions value for a project or single file.
8892
// It also allows a 'cheaper' route to get the project options relevant to parsing (e.g. the #define values).
8993
// The main entrypoints are TryGetOptionsForDocumentOrProject and TryGetOptionsForEditingDocumentOrProject.
90-
91-
9294
[<Export(typeof<FSharpProjectOptionsManager>); Composition.Shared>]
9395
type internal FSharpProjectOptionsManager
9496
[<ImportingConstructor>]
9597
(
9698
checkerProvider: FSharpCheckerProvider,
99+
[<Import(typeof<VisualStudioWorkspace>)>] workspace: VisualStudioWorkspaceImpl,
97100
[<Import(typeof<SVsServiceProvider>)>] serviceProvider: System.IServiceProvider
98101
) =
99-
// A table of information about projects, excluding single-file projects.
102+
103+
// A table of information about projects, excluding single-file projects.
100104
let projectTable = ConcurrentDictionary<ProjectId, Refreshable<ProjectId[] * FSharpProjectOptions>>()
101105

102106
// A table of information about single-file projects. Currently we only need the load time of each such file, plus
103107
// the original options for editing
104108
let singleFileProjectTable = ConcurrentDictionary<ProjectId, DateTime * FSharpProjectOptions>()
105109

110+
// Accumulate sources and references for each project file
111+
let projectInfo = new ConcurrentDictionary<string, string[]*string[]*string[]>()
112+
113+
let projectDisplayNameOf projectFileName =
114+
if String.IsNullOrWhiteSpace projectFileName then projectFileName
115+
else Path.GetFileNameWithoutExtension projectFileName
116+
117+
let tryGetOrCreateProjectId (projectFileName: string) =
118+
let projectDisplayName = projectDisplayNameOf projectFileName
119+
Some (workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectDisplayName))
120+
106121
/// Clear a project from the project table
107122
member this.ClearInfoForProject(projectId: ProjectId) =
108123
projectTable.TryRemove(projectId) |> ignore
@@ -124,7 +139,7 @@ type internal FSharpProjectOptionsManager
124139

125140
member this.AddOrUpdateSingleFileProject(projectId, data) =
126141
singleFileProjectTable.[projectId] <- data
127-
142+
128143
/// Get the exact options for a single-file script
129144
member this.ComputeSingleFileOptions (tryGetOrCreateProjectId, fileName, loadTime, fileContents, workspace: Workspace) = async {
130145
let extraProjectInfo = Some(box workspace)
@@ -145,15 +160,15 @@ type internal FSharpProjectOptionsManager
145160
}
146161

147162
/// Update the info for a project in the project table
148-
member this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId: ProjectId, site: IProjectSite, workspace: Workspace, userOpName) =
163+
member this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId: ProjectId, site: IProjectSite, userOpName) =
149164
this.AddOrUpdateProject(projectId, (fun isRefresh ->
150165
let extraProjectInfo = Some(box workspace)
151166
let tryGetOptionsForReferencedProject f = f |> tryGetOrCreateProjectId |> Option.bind this.TryGetOptionsForProject
152167
let referencedProjects, options = ProjectSitesAndFiles.GetProjectOptionsForProjectSite(Settings.LanguageServicePerformance.EnableInMemoryCrossProjectReferences, tryGetOptionsForReferencedProject, site, site.ProjectFileName(), extraProjectInfo, serviceProvider, true)
153168
let referencedProjectIds = referencedProjects |> Array.choose tryGetOrCreateProjectId
154169
checkerProvider.Checker.InvalidateConfiguration(options, startBackgroundCompileIfAlreadySeen = not isRefresh, userOpName= userOpName + ".UpdateProjectInfo")
155170
referencedProjectIds, options))
156-
171+
157172
/// Get compilation defines relevant for syntax processing.
158173
/// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project
159174
/// options for a script.
@@ -204,6 +219,84 @@ type internal FSharpProjectOptionsManager
204219
| true, (_loadTime, originalOptions) -> Some originalOptions
205220
| _ -> this.TryGetOptionsForProject(projectId)
206221

222+
member this.ProvideProjectSiteProvider(project:Project) =
223+
let hier = workspace.GetHierarchy(project.Id)
224+
225+
{new IProvideProjectSite with
226+
member iProvideProjectSite.GetProjectSite() =
227+
let fst (a, _, _) = a
228+
let thrd (_, _, c) = c
229+
let mutable errorReporter =
230+
let reporter = ProjectExternalErrorReporter(project.Id, "FS", serviceProvider)
231+
Some(reporter:> Microsoft.VisualStudio.Shell.Interop.IVsLanguageServiceBuildErrorReporter2)
232+
233+
{new Microsoft.VisualStudio.FSharp.LanguageService.IProjectSite with
234+
member __.SourceFilesOnDisk() = this.GetProjectInfo(project.FilePath) |> fst
235+
member __.DescriptionOfProject() = project.Name
236+
member __.CompilerFlags() =
237+
let _,references,options = this.GetProjectInfo(project.FilePath)
238+
Array.concat [options; references |> Array.map(fun r -> "-r:" + r)]
239+
member __.ProjectFileName() = project.FilePath
240+
member __.AdviseProjectSiteChanges(_,_) = ()
241+
member __.AdviseProjectSiteCleaned(_,_) = ()
242+
member __.AdviseProjectSiteClosed(_,_) = ()
243+
member __.IsIncompleteTypeCheckEnvironment = false
244+
member __.TargetFrameworkMoniker = ""
245+
member __.ProjectGuid = project.Id.Id.ToString()
246+
member __.LoadTime = System.DateTime.Now
247+
member __.ProjectProvider = Some iProvideProjectSite
248+
member __.AssemblyReferences() = this.GetProjectInfo(project.FilePath) |> thrd
249+
member __.BuildErrorReporter with get () = errorReporter and
250+
set (v) = errorReporter <- v
251+
}
252+
253+
// TODO: figure out why this is necessary
254+
interface IVsHierarchy with
255+
member __.SetSite(psp) = hier.SetSite(psp)
256+
member __.GetSite(psp) = hier.GetSite(ref psp)
257+
member __.QueryClose(pfCanClose) = hier.QueryClose(ref pfCanClose)
258+
member __.Close() = hier.Close()
259+
member __.GetGuidProperty(itemid, propid, pguid) = hier.GetGuidProperty(itemid, propid, ref pguid)
260+
member __.SetGuidProperty(itemid, propid, rguid) = hier.SetGuidProperty(itemid, propid, ref rguid)
261+
member __.GetProperty(itemid, propid, pvar) = hier.GetProperty(itemid, propid, ref pvar)
262+
member __.SetProperty(itemid, propid, var) = hier.SetProperty(itemid, propid, var)
263+
member __.GetNestedHierarchy(itemid, iidHierarchyNested, ppHierarchyNested, pitemidNested) = hier.GetNestedHierarchy(itemid, ref iidHierarchyNested, ref ppHierarchyNested, ref pitemidNested)
264+
member __.GetCanonicalName(itemid, pbstrName) = hier.GetCanonicalName(itemid, ref pbstrName)
265+
member __.ParseCanonicalName(pszName, pitemid) = hier.ParseCanonicalName(pszName, ref pitemid)
266+
member __.Unused0() = hier.Unused0()
267+
member __.AdviseHierarchyEvents(pEventSink, pdwCookie) = hier.AdviseHierarchyEvents(pEventSink, ref pdwCookie)
268+
member __.UnadviseHierarchyEvents(dwCookie) = hier.UnadviseHierarchyEvents(dwCookie)
269+
member __.Unused1() = hier.Unused1()
270+
member __.Unused2() = hier.Unused2()
271+
member __.Unused3() = hier.Unused3()
272+
member __.Unused4() = hier.Unused4()
273+
}
274+
275+
member this.UpdateProjectInfoWithProjectId(projectId:ProjectId, userOpName) =
276+
let project = workspace.CurrentSolution.GetProject(projectId)
277+
let siteProvider = this.ProvideProjectSiteProvider(project)
278+
this.UpdateProjectInfo(tryGetOrCreateProjectId, projectId, siteProvider.GetProjectSite(), userOpName)
279+
280+
member this.UpdateProjectInfoWithPath(path, userOpName) =
281+
let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(path, projectDisplayNameOf path)
282+
this.UpdateProjectInfoWithProjectId(projectId, userOpName)
283+
284+
[<Export>]
285+
/// This handles commandline change notifications from the Dotnet Project-system
286+
member this.HandleCommandLineChanges(path:string, sources:ImmutableArray<CommandLineSourceFile>, references:ImmutableArray<CommandLineReference>, options:ImmutableArray<string>) =
287+
let fullPath p =
288+
if Path.IsPathRooted(p) then p
289+
else Path.Combine(Path.GetDirectoryName(path), p)
290+
let sourcePaths = sources |> Seq.map(fun s -> fullPath s.Path) |> Seq.toArray
291+
let referencePaths = references |> Seq.map(fun r -> fullPath r.Reference) |> Seq.toArray
292+
projectInfo.[path] <- (sourcePaths,referencePaths,options.ToArray())
293+
this.UpdateProjectInfoWithPath(path, "HandleCommandLineChanges")
294+
295+
member __.GetProjectInfo(path:string) =
296+
match projectInfo.TryGetValue path with
297+
| true, value -> value
298+
| _ -> [||], [||], [||]
299+
207300
// Used to expose FSharpChecker/ProjectInfo manager to diagnostic providers
208301
// Diagnostic providers can be executed in environment that does not use MEF so they can rely only
209302
// on services exposed by the workspace
@@ -217,7 +310,6 @@ type internal RoamingProfileStorageLocation(keyName: string) =
217310

218311
member __.GetKeyNameForLanguage(languageName: string) =
219312
let unsubstitutedKeyName = keyName
220-
221313
match languageName with
222314
| null -> unsubstitutedKeyName
223315
| _ ->
@@ -266,21 +358,15 @@ type
266358

267359
override this.Initialize() =
268360
base.Initialize()
269-
//initialize settings
270361
this.ComponentModel.GetService<SettingsPersistence.ISettings>() |> ignore
271362

272363
override this.RoslynLanguageName = FSharpConstants.FSharpLanguageName
273-
274364
override this.CreateWorkspace() = this.ComponentModel.GetService<VisualStudioWorkspaceImpl>()
275-
276-
override this.CreateLanguageService() =
277-
FSharpLanguageService(this)
278-
365+
override this.CreateLanguageService() = FSharpLanguageService(this)
279366
override this.CreateEditorFactories() = Seq.empty<IVsEditorFactory>
280-
281367
override this.RegisterMiscellaneousFilesWorkspaceInformation(_) = ()
282-
283-
and
368+
369+
type
284370
[<Guid(FSharpConstants.languageServiceGuidString)>]
285371
[<ProvideLanguageExtension(typeof<FSharpLanguageService>, ".fs")>]
286372
[<ProvideLanguageExtension(typeof<FSharpLanguageService>, ".fsi")>]
@@ -299,7 +385,7 @@ and
299385

300386
let projectInfoManager = package.ComponentModel.DefaultExportProvider.GetExport<FSharpProjectOptionsManager>().Value
301387

302-
let projectDisplayNameOf projectFileName =
388+
let projectDisplayNameOf projectFileName =
303389
if String.IsNullOrWhiteSpace projectFileName then projectFileName
304390
else Path.GetFileNameWithoutExtension projectFileName
305391

@@ -321,15 +407,20 @@ and
321407

322408
let optionsAssociation = ConditionalWeakTable<IWorkspaceProjectContext, string[]>()
323409

410+
member private this.OnProjectAdded(projectId:ProjectId, _newSolution:Solution) = projectInfoManager.UpdateProjectInfoWithProjectId(projectId, "OnProjectAdded")
324411
override this.Initialize() =
325412
base.Initialize()
326413

414+
let workspaceChanged (args:WorkspaceChangeEventArgs) =
415+
match args.Kind with
416+
| WorkspaceChangeKind.ProjectAdded -> this.OnProjectAdded(args.ProjectId, args.NewSolution)
417+
| _ -> ()
418+
327419
this.Workspace.Options <- this.Workspace.Options.WithChangedOption(Completion.CompletionOptions.BlockForCompletionItems, FSharpConstants.FSharpLanguageName, false)
328420
this.Workspace.Options <- this.Workspace.Options.WithChangedOption(Shared.Options.ServiceFeatureOnOffOptions.ClosedFileDiagnostic, FSharpConstants.FSharpLanguageName, Nullable false)
421+
this.Workspace.WorkspaceChanged.Add(workspaceChanged)
422+
this.Workspace.DocumentClosed.Add <| fun args -> tryRemoveSingleFileProject args.Document.Project.Id
329423

330-
this.Workspace.DocumentClosed.Add <| fun args ->
331-
tryRemoveSingleFileProject args.Document.Project.Id
332-
333424
Events.SolutionEvents.OnAfterCloseSolution.Add <| fun _ ->
334425
//checkerProvider.Checker.StopBackgroundCompile()
335426

@@ -342,7 +433,7 @@ and
342433
singleFileProjects.Keys |> Seq.iter tryRemoveSingleFileProject
343434

344435
let ctx = System.Threading.SynchronizationContext.Current
345-
436+
346437
let rec setupProjectsAfterSolutionOpen() =
347438
async {
348439
use openedProjects = MailboxProcessor.Start <| fun inbox ->
@@ -368,27 +459,27 @@ and
368459

369460
let theme = package.ComponentModel.DefaultExportProvider.GetExport<ISetThemeColors>().Value
370461
theme.SetColors()
371-
462+
372463
/// Sync the information for the project
373-
member this.SyncProject(project: AbstractProject, projectContext: IWorkspaceProjectContext, site: IProjectSite, workspace, forceUpdate, userOpName) =
464+
member __.SyncProject(project: AbstractProject, projectContext: IWorkspaceProjectContext, site: IProjectSite, workspace, forceUpdate, userOpName) =
374465
let wellFormedFilePathSetIgnoreCase (paths: seq<string>) =
375466
HashSet(paths |> Seq.filter isPathWellFormed |> Seq.map (fun s -> try System.IO.Path.GetFullPath(s) with _ -> s), StringComparer.OrdinalIgnoreCase)
376467

377468
let updatedFiles = site.SourceFilesOnDisk() |> wellFormedFilePathSetIgnoreCase
378469
let originalFiles = project.GetCurrentDocuments() |> Seq.map (fun file -> file.FilePath) |> wellFormedFilePathSetIgnoreCase
379-
470+
380471
let mutable updated = forceUpdate
381472

382473
for file in updatedFiles do
383474
if not(originalFiles.Contains(file)) then
384475
projectContext.AddSourceFile(file)
385476
updated <- true
386-
477+
387478
for file in originalFiles do
388479
if not(updatedFiles.Contains(file)) then
389480
projectContext.RemoveSourceFile(file)
390481
updated <- true
391-
482+
392483
let updatedRefs = site.AssemblyReferences() |> wellFormedFilePathSetIgnoreCase
393484
let originalRefs = project.GetCurrentMetadataReferences() |> Seq.map (fun ref -> ref.FilePath) |> wellFormedFilePathSetIgnoreCase
394485

@@ -423,7 +514,7 @@ and
423514

424515
// update the cached options
425516
if updated then
426-
projectInfoManager.UpdateProjectInfo(tryGetOrCreateProjectId workspace, project.Id, site, project.Workspace, userOpName + ".SyncProject")
517+
projectInfoManager.UpdateProjectInfo(tryGetOrCreateProjectId workspace, project.Id, site, userOpName + ".SyncProject")
427518

428519
member this.SetupProjectFile(siteProvider: IProvideProjectSite, workspace: VisualStudioWorkspaceImpl, userOpName) =
429520
let userOpName = userOpName + ".SetupProjectFile"
@@ -435,7 +526,7 @@ and
435526
let projectId = workspace.ProjectTracker.GetOrCreateProjectIdForPath(projectFileName, projectDisplayName)
436527

437528
if isNull (workspace.ProjectTracker.GetProject projectId) then
438-
projectInfoManager.UpdateProjectInfo(tryGetOrCreateProjectId workspace, projectId, site, workspace, userOpName)
529+
projectInfoManager.UpdateProjectInfo(tryGetOrCreateProjectId workspace, projectId, site, userOpName)
439530
let projectContextFactory = package.ComponentModel.GetService<IWorkspaceProjectContextFactory>();
440531
let errorReporter = ProjectExternalErrorReporter(projectId, "FS", this.SystemServiceProvider)
441532

@@ -474,7 +565,6 @@ and
474565
setup (siteProvider.GetProjectSite()) |> ignore
475566

476567
member this.SetupStandAloneFile(fileName: string, fileContents: string, workspace: VisualStudioWorkspaceImpl, hier: IVsHierarchy) =
477-
478568
let loadTime = DateTime.Now
479569
let projectFileName = fileName
480570
let projectDisplayName = projectDisplayNameOf projectFileName
@@ -506,19 +596,30 @@ and
506596
base.SetupNewTextView(textView)
507597

508598
let textViewAdapter = package.ComponentModel.GetService<IVsEditorAdaptersFactoryService>()
509-
599+
510600
match textView.GetBuffer() with
511601
| (VSConstants.S_OK, textLines) ->
512602
let filename = VsTextLines.GetFilename textLines
603+
604+
// CPS projects don't implement IProvideProjectSite and IVSProjectHierarchy
605+
// Simple explanation:
606+
// Legacy projects have IVSHierarchy and IPRojectSite
607+
// CPS Projects and loose script files don't
513608
match VsRunningDocumentTable.FindDocumentWithoutLocking(package.RunningDocumentTable,filename) with
514609
| Some (hier, _) ->
515610
match hier with
516-
| :? IProvideProjectSite as siteProvider when not (IsScript(filename)) ->
611+
| :? IProvideProjectSite as siteProvider when not (IsScript(filename)) ->
517612
this.SetupProjectFile(siteProvider, this.Workspace, "SetupNewTextView")
518-
| _ ->
613+
| _ when not (IsScript(filename)) ->
614+
let docId = this.Workspace.CurrentSolution.GetDocumentIdsWithFilePath(filename).FirstOrDefault()
615+
match docId with
616+
| null ->
617+
let fileContents = VsTextLines.GetFileContents(textLines, textViewAdapter)
618+
this.SetupStandAloneFile(filename, fileContents, this.Workspace, hier)
619+
| id ->
620+
projectInfoManager.UpdateProjectInfoWithProjectId(id.ProjectId, "SetupNewTextView")
621+
| _ ->
519622
let fileContents = VsTextLines.GetFileContents(textLines, textViewAdapter)
520623
this.SetupStandAloneFile(filename, fileContents, this.Workspace, hier)
521624
| _ -> ()
522625
| _ -> ()
523-
524-

0 commit comments

Comments
 (0)