Skip to content
This repository has been archived by the owner on Oct 31, 2021. It is now read-only.

Project cache invalidating #72

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<ItemGroup>
<Reference Include="EnvDTE, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
</Reference>
<Reference Include="EnvDTE80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="FSharp.Compiler.Service">
<HintPath>..\..\packages\FSharp.Compiler.Service.0.0.20\lib\net40\FSharp.Compiler.Service.dll</HintPath>
<Private>True</Private>
Expand Down
2 changes: 1 addition & 1 deletion src/FSharpVSPowerTools.Logic/HighlightUsageTagger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ type HighlightUsageTagger(view : ITextView, sourceBuffer : ITextBuffer, textSear
let doc = Dte.getActiveDocument()

maybe {
let! project = ProjectProvider.get doc
let! project = ProjectsCache.getProject doc
let! newWord = VSLanguageService.getSymbol currentRequest project
// If this is the same word we currently have, we're done (e.g. caret moved within a word).
match currentWord with
Expand Down
5 changes: 4 additions & 1 deletion src/FSharpVSPowerTools.Logic/LanguageService.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ open Microsoft.FSharp.Compiler.SourceCodeServices
module VSLanguageService =
// TODO: we should reparse the stale document and cache it
let Instance = FSharp.CompilerBinding.LanguageService(fun _ -> ())
do SolutionEvents.ProjectChanged.Add (fun _ ->
debug "[Language Service] InteractiveChecker.InvalidateAll"
Instance.Checker.InvalidateAll())

let getSymbol (point: SnapshotPoint) (projectProvider : ProjectProvider) =
let source = point.Snapshot.GetText()
Expand All @@ -34,7 +37,7 @@ module VSLanguageService =
| [||] -> [| currentFile |]
| files -> files

debug "Get symbol references for '%s' at line %d col %d on %A framework and '%s' arguments"
debug "[Language Service] Get symbol references for '%s' at line %d col %d on %A framework and '%s' arguments"
(word.GetText()) endLine endCol framework (String.concat " " args)

return! Instance.GetUsesOfSymbolAtLocation(projectFileName, currentFile, source, sourceFiles,
Expand Down
35 changes: 20 additions & 15 deletions src/FSharpVSPowerTools.Logic/ProjectProvider.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ open System.Diagnostics
open EnvDTE
open VSLangProj
open FSharp.CompilerBinding
open FSharpVSPowerTools

type ProjectProvider(project : VSProject) =
do Debug.Assert(project <> null && project.Project <> null, "Input project should be well-formed.")
Expand Down Expand Up @@ -56,7 +57,7 @@ type ProjectProvider(project : VSProject) =
yield! this.References
|]
| None ->
Debug.WriteLine("[Project System] Can't read project file. Fall back to default compiler flags.")
debug "[Project System] Can't read project file. Fall back to default compiler flags."
[|
yield "--noframework"
yield "--debug-"
Expand All @@ -70,9 +71,9 @@ type ProjectProvider(project : VSProject) =
| Some p ->
ProjectParser.getFiles p
| None ->
Debug.WriteLine("[Project System] Can't read project file. Fall back to incomplete source files.")
debug "[Project System] Can't read project file. Fall back to incomplete source files."
let projectItems = project.Project.ProjectItems
Debug.Assert(Seq.cast<ProjectItem> projectItems <> null && projectItems.Count > 0, "Should have file names in the project.")
Debug.Assert (Seq.cast<ProjectItem> projectItems <> null && projectItems.Count > 0, "Should have file names in the project.")
projectItems
|> Seq.cast<ProjectItem>
|> Seq.filter (fun item -> try item.Document <> null with _ -> false)
Expand All @@ -82,38 +83,42 @@ type ProjectProvider(project : VSProject) =
|> Seq.map (fun item -> Path.Combine(currentDir, item.Properties.["FileName"].Value.ToString()))
|> Seq.toArray

[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module ProjectProvider =
open FSharpVSPowerTools

type private Message = Get of Document * AsyncReplyChannel<ProjectProvider option>
/// Cache of ProjectProviders. Listens for projects changes and invalidates itself.
module ProjectsCache =
type private Message =
| Get of Document * AsyncReplyChannel<ProjectProvider option>
| Remove of projectUniqueName:string

let private agent = MailboxProcessor.Start(fun inbox ->
let rec loop (projects: Map<string, ProjectProvider>) = async {
let! msg = inbox.Receive()
match msg with
| Get (doc, r) ->
let project =
try Option.ofNull (doc.ProjectItem.ContainingProject.Object :?> VSProject)
with _ -> None
doc.ProjectItem.VSProject
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure doc.ProjectItem could be null in some case.

|> Option.bind (fun vsProject ->
match projects |> Map.tryFind vsProject.Project.UniqueName with
| None ->
try
debug "Creating new project provider."
debug "[ProjectsCache] Creating new project provider."
Some (ProjectProvider (vsProject))
with _ ->
debug "Can't find containing project. Probably the document is opened in an ad-hoc way."
debug "[ProjectsCache] Can't find containing project. Probably the document is opened in an ad-hoc way."
None
| x -> debug "Found cached project provider."; x)
| x -> debug "[ProjectsCache] Found cached project provider."; x)

r.Reply project
let projects =
match project with
| Some p -> projects |> Map.add p.UniqueName p
| _ -> projects
return! loop projects }
return! loop projects
| Remove uniqueProjectName ->
debug "[ProjectsCache] %s has been removed from cache." uniqueProjectName
return! loop (projects |> Map.remove uniqueProjectName) }
loop Map.empty)

SolutionEvents.ProjectChanged.Add (fun vsProject -> agent.Post (Remove vsProject.Project.UniqueName))
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar comments as in the ProjectItemsEvents above.


/// Returns ProjectProvider for given Documents (it caches ProjectProviders forever for now).
let get document = agent.PostAndReply (fun r -> Get (document, r))
let getProject document = agent.PostAndReply (fun r -> Get (document, r))
2 changes: 1 addition & 1 deletion src/FSharpVSPowerTools.Logic/RenameCommandFilter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type RenameCommandFilter(view : IWpfTextView, serviceProvider : System.IServiceP
maybe {
let! point = view.TextBuffer.GetSnapshotPoint caretPosition
let doc = Dte.getActiveDocument()
let! project = ProjectProvider.get doc
let! project = ProjectsCache.getProject doc
state <- Some
{ File = doc.FullName
Project = project
Expand Down
25 changes: 25 additions & 0 deletions src/FSharpVSPowerTools.Logic/VSUtils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,28 @@ module Dte =
System.Diagnostics.Debug.Assert(doc <> null && doc.ProjectItem.ContainingProject <> null,
"Should be able to find active document and active project.")
doc

type ProjectItem with
member x.VSProject =
Option.ofNull x
|> Option.bind (fun item ->
try Option.ofNull (item.ContainingProject.Object :?> VSProject) with _ -> None)

module SolutionEvents =
let private projectChanged = Event<_>()
let private dte = Package.GetGlobalService(typedefof<DTE>) :?> DTE
let private events = dte.Events :?> EnvDTE80.Events2
Copy link
Contributor

Choose a reason for hiding this comment

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

These two dynamic castings could raise InvalidCaseException. Might be good idea to wrap them in try/with and process null case.


let private onProjectChanged (projectItem: ProjectItem) =
projectItem.VSProject
|> Option.iter (fun item ->
debug "[ProjectsCache] %s changed." projectItem.Name
item.Project.Save()
projectChanged.Trigger item)

events.ProjectItemsEvents.add_ItemRenamed (fun p _ -> onProjectChanged p)
events.ProjectItemsEvents.add_ItemRemoved (fun p -> onProjectChanged p)
events.ProjectItemsEvents.add_ItemAdded (fun p -> onProjectChanged p)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is a good place for subscribing events. This works on some order of module evaluation but might not work on other orders.

A better place could be the Initialize method where the package has been initialized correctly https://github.com/fsprojects/FSharpVSPowerTools/blob/master/src/FSharpVSPowerTools/PowerToolsCommandsPackage.cs#L45.


/// Raised when any project in solution has changed.
let ProjectChanged = projectChanged.Publish