Last active
July 15, 2023 02:43
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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