Skip to content

Instantly share code, notes, and snippets.

@SeeminglyScience
Last active July 15, 2023 02:43
Show Gist options
  • Save SeeminglyScience/b6e42ac46710ff5b775f35ded43a35e6 to your computer and use it in GitHub Desktop.
Save SeeminglyScience/b6e42ac46710ff5b775f35ded43a35e6 to your computer and use it in GitHub Desktop.
Snippet for enabling cross module "Find All References", "Go to Definition" and intellisense in VSCode PowerShell
if ($psEditor) {
# Don't reference any files whose FullName match this regex.
${Exclude Files Regex} = '\\Release\\|\\\.vscode\\|build.*\.ps1|debugHarness\.ps1|\.psd1'
# Get the PowerShellEditorServices assemblies.
${editor services assemblies} = [System.AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object Location -Match 'PowerShell.EditorServices.*.dll' |
ForEach-Object -MemberName Location
# Add some C# that essentially lets us add a hook to the event that is called when VSCode opens a file.
Add-Type -Language CSharp -ReferencedAssemblies ${editor services assemblies} -WarningAction SilentlyContinue -TypeDefinition @'
using Microsoft.PowerShell.EditorServices.Protocol.LanguageServer;
using Microsoft.PowerShell.EditorServices.Protocol.MessageProtocol;
using Microsoft.PowerShell.EditorServices.Protocol.Server;
using Microsoft.PowerShell.EditorServices;
using System.Management.Automation;
using System.Threading.Tasks;
using System.Reflection;
public class ProxyLanguageServer
{
private LanguageServer languageServer;
public ProxyLanguageServer(LanguageServer languageServer)
{
this.languageServer = languageServer;
this.languageServer.SetEventHandler(
DidOpenTextDocumentNotification.Type,
this.HandleDidOpenTextDocumentNotification,
true);
}
protected Task HandleDidOpenTextDocumentNotification(
DidOpenTextDocumentNotification openParams,
EventContext eventContext)
{
EditorSession editorSession = languageServer
.GetType()
.GetField("editorSession", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(languageServer) as EditorSession;
MethodInfo originalMethod = languageServer
.GetType()
.GetMethod("HandleDidOpenTextDocumentNotification", BindingFlags.NonPublic | BindingFlags.Instance);
originalMethod.Invoke(languageServer, new object[] { openParams, eventContext });
var psCommand = new PSCommand();
psCommand.AddCommand("Update-PSEditorReferenceList");
editorSession.PowerShellContext.ExecuteCommand(psCommand);
return Task.FromResult(true);
}
}
'@
# Get the list of all files in the workspace.
${workspace files} = Get-ChildItem -Path $psEditor.Workspace.Path -Filter '*.ps*1' -Recurse |
Where-Object FullName -NotMatch ${Exclude Files Regex} |
ForEach-Object -MemberName FullName
# Set up some variables we'll use throughout. The reason for the awkward names is so they are
# unlikely to conflict with normal use.
${f l a g s} = [System.Reflection.BindingFlags]'NonPublic,Instance'
${editor operations} = $psEditor.GetType().
GetField('editorOperations', ${f l a g s}).
GetValue($psEditor)
${editor session} = ${editor operations}.GetType().
GetField('editorSession', ${f l a g s}).
GetValue(${editor operations})
${language server} = ${editor operations}.GetType().
GetField('messageSender', ${f l a g s}).
GetValue(${editor operations})
[ProxyLanguageServer]::new(${language server}) | Out-Null
# Creates a Microsoft.PowerShell.EditorServices.ScriptFile object from a file path.
function New-PSEditorScriptFile {
[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')]
param([string[]]$Path)
$Path = Resolve-Path $Path -ErrorAction Stop
foreach ($file in $Path) {
${editor session}.Workspace.GetFile($file)
}
}
# Tells EditorServices to pretend all files explicitly reference each other.
function Update-PSEditorReferenceList {
[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '')]
param()
# Get the current list of open files.
$openFiles = ${editor session}.Workspace.GetType().
GetField('workspaceFiles', ${f l a g s}).
GetValue(${editor session}.Workspace)
foreach ($openFile in $openFiles.GetEnumerator()) {
[string[]]$newReferencedFiles = $openfile.Value.ReferencedFiles
# Ensure all workspace files are in the referenced file list for every file.
foreach ($workspaceFile in ${workspace files}) {
if (-not $openFile.Value.ReferencedFiles -or -not $openFile.Value.ReferencedFiles.Contains($workspaceFile)) {
$newReferencedFiles += $workspaceFile
}
}
# Set the private field for referenced files because the property is read only.
$openFile.Value.GetType().
GetField('<ReferencedFiles>k__BackingField', ${f l a g s}).
SetValue($openFile.Value, $newReferencedFiles)
}
}
Update-PSEditorReferenceList
# Load all workspace functions into intellisense. If you use using statements make sure to specify the
# full type name in anything that generates metadata (e.g. OutputType, parameter type constraints, etc)
# If you don't, PowerShell will crash when it tries to pull up intellisense.
${workspace files} | ForEach-Object {
$ast = [System.Management.Automation.Language.Parser]::ParseFile($PSItem, [ref]$null, [ref]$null)
$functionDefinitions = $ast.FindAll({$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]},$true)
. ([scriptblock]::Create($functionDefinitions.Extent.Text))
}
Remove-Variable -Name ast, functionDefinitions -ErrorAction Ignore
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment