ScriptPredictor.psm1
using namespace System.Collections.Generic using namespace System.Management.Automation using namespace System.Management.Automation.Subsystem using namespace System.Management.Automation.Subsystem.Prediction using namespace System.Threading using namespace ScriptPredictor Add-Type -Path $(Join-Path $PSScriptRoot '*.dll') # This "works" but because the scriptblocks get marshaled to the default runspace, it ends up being blocking and bad performance. # class ScriptPredictor : ICommandPredictor { # [ScriptBlock]$ScriptBlock; # [string]$Name; # [string]$Description; # [guid]$Id; # ScriptPredictor([ScriptBlock]$ScriptBlock, [string]$Name, [string]$Description, [guid]$Id) { # $this.ScriptBlock = $ScriptBlock; # $this.Name = $Name; # $this.Description = $Description; # $this.Id = $Id ?? [Guid]::NewGuid(); # } # [SuggestionPackage] GetSuggestion( # [PredictionClient]$client, # [PredictionContext]$context, # [CancellationToken]$cancellationToken # ) { # [Console]::WriteLine('test') # [Console]::WriteLine('test2') # $suggestions = $this.ScriptBlock.Invoke($Context); # $formattedSuggestions = foreach ($suggestion in $suggestions) { # if ($suggestion -is [PredictiveSuggestion]) { # $suggestion # } elseif ($suggestion -is [string]) { # [PredictiveSuggestion]$suggestion # } else { # throw "ScriptPredictor $($this.Name) [$($this.Id)]: ScriptBlock returned objects that arent a string or a [PredictiveSuggestion] object. Unexpected Object Type: $($suggestion.GetType())" # } # } # return [SuggestionPackage]::new($formattedSuggestions) # } # # TODO: Provide hooks for this feedback behavior # [bool] CanAcceptFeedback([PredictionClient]$client, [PredictorFeedbackKind]$feedback) { return $false } # [void] OnCommandLineAccepted([PredictionClient]$client, [IReadOnlyList[string]]$history) {} # [void] OnCommandLineExecuted([PredictionClient]$client, [string]$commandLine, [bool]$success) {} # [void] OnSuggestionAccepted([PredictionClient]$client, [uint]$session, [string]$acceptedSuggestion) {} # [void] OnSuggestionDisplayed([PredictionClient]$client, [uint]$session, [int]$countOrIndex) {} # } function Register-ScriptPredictor { <# .SYNOPSIS Registers a scriptblock as a PowerShell Predictor. This works very similarly to an ArgumentCompleter. Your scriptblock should take a [PredictionContext] as a parameter, and must return zero or more [PredictiveSuggestion] objects .EXAMPLE Register-ScriptPredictor { try{ $args[0].InputScript.Text + (Get-Random) } catch { } } A simple registration that adds a random number to your current input #> param( #The scriptblock for the predictor. It runs in its own runspace and can't use any external state except .NET static objects/methods. You can use either $args[0] to access the [ParameterContext] object, or use param([ParameterContext]$context). If you use the latter, you will get Intellisense in VSCode for the object. [Parameter(ValueFromPipeline)][ScriptBlock]$ScriptBlock, #The name of your predictor as will be seen in the subsystem registration. This defaults to 'ScriptPredictor' [ValidateNotNullOrEmpty()] [string]$Name = 'ScriptPredictor', #A description of your predictor. This defaults to the same as the Name. [ValidateNotNullOrEmpty()] [string]$Description = $Name, #Optionally specify a custom GUID for your predictor. A random one will be generated instead if you do not. [ValidateNotNullOrEmpty()] [guid]$Id = $(New-Guid) ) $ErrorActionPreference = 'Stop' [ScriptPredictor]$predictor = [ScriptPredictor]::new( $ScriptBlock, $Name, $Description, $Id ) #TODO: Add a quick script verification to sanity check it has the right parameters and maybe run a test for output [SubsystemManager]::RegisterSubsystem([SubsystemKind]::CommandPredictor, $predictor) } |