You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Find reference code lens results should be cached #678
Right now the references code lens searches the AST of every file in the work space for references every document edit. This causes intellisense to crawl in workspaces with a lot of PowerShell files.
We should cache the commands each workspace file references until they are changed.
In terms of implementation, I can imagine us caching the AST of files (a table of filepaths => ASTs) pretty easily. But I see you've written command caching. Do you mean we should cache the commands once they're parsed (as AST subunits)?
If so, would it make sense for us to do the full AST caching and then process that to create a command cache and make invalidation work backwards (so changing a file invalidates the associated AST, which in turn bubbles up to the command cache)?
I was under the impression we were already caching the AST. I thought we stored them as ScriptFile's and then just searched the AST with an AstVisitor each edit. If that is the case then we would only need to add a cache of what symbols it references. We'd have to change the visitor to find all symbol references instead of a specific symbol reference. That may be another performance save though, as right now we search the AST once for every lens.
If I'm wrong and we don't do that, then yes absolutely we should cache the AST asap. That'd be a huge performance difference especially around DSC.
The way I picture it being implemented is as a lazy property on ScriptFile. Then we could just clear it when ScriptFile.ApplyChange is invoked. If we aren't caching the ScriptFile either, that'd probably be step one.
Now lets say you have Script C open. The way code lens current works is like this:
VSCode says "give me code lenses for Script C"
PSES sees two function definitions to build references for
PSES starts with Invoke-MyCommand, it enumerates the files in the workspace, and scans the AST of all files for references to Invoke-MyCommand.
PSES moves on to Invoke-MyCommand2, it then enumerates the files in the workspace again and rescans the AST of all files for references to Invoke-MyCommand.
Here's how I propose that it works:
VSCode says "give me code lenses for Script C"
PSES sees two function definitions to build references for
PSES starts with Invoke-MyCommand. It enumerates the cached ScriptFile objects in the workspace and does the following for each file.
Checks a lazy property that holds every referenced command. We don't want to build this property every change because it may never be requested.
If the lazy property is null, search the AST of the ScriptFile for every CommandAst and store the result of CommandAst.GetCommandName() and store it as a HashSet<> (warning: result can be null)
If the lazy property is not null, check to see if Invoke-MyCommand is in the HashSet<>
The last step is repeated for Invoke-MyCommand2, but this time all the lazy properties should be filled.
That way every AST is only searched once per change of that file specifically. Right now every AST is searched once for every function definition in the open file, every time that file is changed.
Resolved in the v3.8.0 rewrite — a per-file ReferenceTable caches reference scans and invalidates on edit, so references/CodeLens no longer re-scan everything each keystroke. Closing as completed.
Activity
rjmholt commentedon Jun 5, 2018
In terms of implementation, I can imagine us caching the AST of files (a table of filepaths => ASTs) pretty easily. But I see you've written command caching. Do you mean we should cache the commands once they're parsed (as AST subunits)?
If so, would it make sense for us to do the full AST caching and then process that to create a command cache and make invalidation work backwards (so changing a file invalidates the associated AST, which in turn bubbles up to the command cache)?
SeeminglyScience commentedon Jun 5, 2018
I was under the impression we were already caching the AST. I thought we stored them as
ScriptFile's and then just searched the AST with anAstVisitoreach edit. If that is the case then we would only need to add a cache of what symbols it references. We'd have to change the visitor to find all symbol references instead of a specific symbol reference. That may be another performance save though, as right now we search the AST once for every lens.If I'm wrong and we don't do that, then yes absolutely we should cache the AST asap. That'd be a huge performance difference especially around DSC.
The way I picture it being implemented is as a lazy property on
ScriptFile. Then we could just clear it whenScriptFile.ApplyChangeis invoked. If we aren't caching theScriptFileeither, that'd probably be step one.TylerLeonhardt commentedon Jun 7, 2018
Assigning this to Rob who's going to investigate this next week
rjmholt commentedon Jun 8, 2018
Had a brief look at the code for AST manipulation here.
Can't find any evidence that we're doing any caching at all.
Very much looking forward to adding some! 😈
rjmholt commentedon Jun 8, 2018
Ah it looks like files are cached using Workspace.cs.
SeeminglyScience commentedon Jun 12, 2018
I'm going to fully flesh out what I mean here as it can be kind of confusing to visualize.
Lets say you have three files.
Script A
Script B
Script C
Now lets say you have Script C open. The way code lens current works is like this:
Invoke-MyCommand, it enumerates the files in the workspace, and scans the AST of all files for references toInvoke-MyCommand.Invoke-MyCommand2, it then enumerates the files in the workspace again and rescans the AST of all files for references toInvoke-MyCommand.Here's how I propose that it works:
Invoke-MyCommand. It enumerates the cachedScriptFileobjects in the workspace and does the following for each file.ScriptFilefor everyCommandAstand store the result ofCommandAst.GetCommandName()and store it as aHashSet<>(warning: result can be null)Invoke-MyCommandis in theHashSet<>Invoke-MyCommand2, but this time all the lazy properties should be filled.That way every AST is only searched once per change of that file specifically. Right now every AST is searched once for every function definition in the open file, every time that file is changed.
fflaten commentedon Feb 7, 2023
Fixed by #1917 ?
andyleejordan commentedon Jun 15, 2026
Resolved in the v3.8.0 rewrite — a per-file
ReferenceTablecaches reference scans and invalidates on edit, so references/CodeLens no longer re-scan everything each keystroke. Closing as completed.Drafted by GitHub Copilot (Claude Opus 4.8) at Andy Jordan (@andyleejordan)'s request.