Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map workspace to iprojectsite #3425

Closed
wants to merge 3 commits into from
Closed

Conversation

KevinRansom
Copy link
Member

Use the Roslyn workspace as a datasource for source files and referenced projects for the F# Editor language service.

Note: as of right now the source files are not in project file order.

Was: #3391

override this.Initialize() =
base.Initialize()

let workspaceChanged (args:WorkspaceChangeEventArgs) =
match args.Kind with
| WorkspaceChangeKind.ProjectAdded -> this.OnProjectAdded(args.ProjectId, this.Workspace, args.NewSolution)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think Roslyn will fire ProjectAdded events for all the projects in a solution when SolutionAdded is fired, so we probably need to handle the Solution events too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Pilchie weirdly we do get project added events. I want to dig into these quite a bit more though.

Copy link
Contributor

@dsyme dsyme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work - the approach looks right. Some changes requested

@@ -32,6 +34,12 @@ open Microsoft.VisualStudio.Shell.Interop
open Microsoft.VisualStudio.FSharp.LanguageService
open Microsoft.VisualStudio.ComponentModelHost


module LogFile =
let log (text:string) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this either #if DEBUG or use System.Diagnostics.Trace

referencePaths |> Seq.iter(fun r -> LogFile.log r)
LogFile.log "======================================="

lock lockObject (fun () -> match sourceFiles.TryGetValue path with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normal formatting would use 4-space indenting for this, like this:

lock lockObject (fun () -> 
    match sourceFiles.TryGetValue path with
    | true, _ ->
        sourceFiles.Remove(path) |> ignore
        sourceFiles.Add(path, sourcePaths)
    ...
)

let projectTable = ConcurrentDictionary<ProjectId, Refreshable<ProjectId[] * FSharpProjectOptions>>()

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

// Accumulate sorces and references for each project file
let lockObject = new Object()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normal name for lockObject is syncObj

referencePaths |> Seq.iter(fun r -> LogFile.log r)
LogFile.log "======================================="

lock lockObject (fun () -> match sourceFiles.TryGetValue path with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use sourceFiles.ContainsKey rather than TryGetValue

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I believe you can just do sourceFiles.[path] <- sourcePaths - you don't need o explicitly Remove nor check if the collection already contains the value - the indexer will do what you need

member __.GetSourceFiles(path:string) =
lock lockObject (fun () -> match sourceFiles.TryGetValue path with
| true, value -> value
| _ -> Array.empty<string>)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normal F# practice is to use [| |] for Array.empty, or to use just Array.empty without the type annotation (which is inferred)


projectId
setup (siteProvider.GetProjectSite()) |> ignore

member private __.ProvideProjectSiteProvider(workspace:Workspace, project:Project) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a helper function either outside this class (to prove that it is logically independent of any of the constructs accessible in the class), or as a let function in the class let section, to help show what data it depends in the initialization sequence. Perhaps it can even go in ProjectSitesAndFiles.fs where two other ways of creating IProjectSite are implemented (for scripts and standalone-files).

// Accumulate sorces and references for each project file
let lockObject = new Object()
let sourceFiles = new Dictionary<string, string[]>()
let referenceFiles = new Dictionary<string, string[]>()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have two tables for the information? Just use

let projectInfoFromCommandLineUpdates = new ConcurrentDictionary<string, string[] * string[]>()

or even

let projectInfoFromCommandLineUpdates = 
    new ConcurrentDictionary<string, ImmutableArray<CommandLineSourceFile> * ImmutableArray<CommandLineReference>()

I think I prefer the second - just snapshot the full information we're given since it may be useful in debugging.

Also, in a recent update I renamed ProjectInfoManager to FSharpProjectOptionsManager to indicate that it was about maintaining a table mapping projects to up-to-date FSharpCompileOptions values. It's now been expanded to contain this orthogonal extra information, so perhaps rename back to ProjectInfoManager or ProjectOptionsManager

let sourcePaths = sources |> Seq.map(fun s -> fullPath s.Path) |> Seq.toArray
let referencePaths = references |> Seq.map(fun r -> fullPath r.Reference) |> Seq.toArray

LogFile.log "======================================="
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove in favour of System.Diagnostics.Trace.

let hier = visualStudioWorkspace.GetHierarchy(project.Id)
{new IProvideProjectSite with
member this.GetProjectSite() =
let compileItems () = projectInfoManager.GetSourceFiles(project.FilePath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there's no need to define all these little functions, nor is it clearer - just put the implementations in the object expression

member __.ProjectProvider = Some this
member __.AssemblyReferences() = assemblyReferences ()
}
interface IVsHierarchy with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is an IVsHierarchy implementation is needed here? I do vaguely remember - is it that crappy code in ProjectSitesAndFiles.fs that searches for IVsHierarchy for project references? However if that's the case, then let's document it, and let's also eventually get rid of this in favour of adding an appropriate method into IProjectSite, or even just using the existing AssemblyReferences method

hierarchy,
null,
errorReporter) :?> AbstractProject
| _ as p -> p
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can just be | p -> p


if isNull (workspace.ProjectTracker.GetProject projectId) then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously we used this line to guard against doing work multiple times for the same project as we traverse the project graph

You've moved the check down a bit, but this means that UpdateProjectInfo may now be called many times per project, along with actions such as creating ProjectExternalErrorReporter, and, most critically, the recursive calls and AddProjectReference calls here:

            for referencedSite in ProjectSitesAndFiles.GetReferencedProjectSites (site, this.SystemServiceProvider) do
                let referencedProjectId = setup referencedSite
                project.AddProjectReference(ProjectReference referencedProjectId)

I think you will want to somehow want to guard against doing these things many times as we traverse the project graph.

@dsyme
Copy link
Contributor

dsyme commented Aug 18, 2017

@KevinRansom As part of validating this could you check the before/after times for "devenv.exe CPU usage time at the point we can first perform a file edit" for

devenv tests\projects\stress\big\dense\Dense.sln

devenv tests\projects\stress\huge\dense\Dense.sln

Thanks. Also, we should write out a list of things we need to test manually when changes like this are made, e.g.

  • Create a .NET Framwork Library + App, add a reference from App to Library, test goto-definition, remove the reference, add it back, test goto-definition etc.

  • Same for .NET Core Library + App

@KevinRansom
Copy link
Member Author

@dsyme .... yes I know ... I have some ideas.

@cartermp
Copy link
Contributor

Why is the branch name keys? Is this based on the correct branch?

@KevinRansom
Copy link
Member Author

because halfway through I looked at the keyboard fix, then I cherry picked the fix over.

I think because ... reasons .... (none of which are sensible).

@cartermp
Copy link
Contributor

I see. Is this PR in a good enough state to validate experience-wise?

@KevinRansom
Copy link
Member Author

@cartermp not even close.

@KevinRansom
Copy link
Member Author

Closing in favour of: #3564

@KevinRansom KevinRansom closed this Sep 9, 2017
@KevinRansom KevinRansom deleted the keys branch September 17, 2017 01:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants