@@ -583,7 +583,7 @@ internal Task LoadHostProfilesAsync(CancellationToken cancellationToken)
583583
584584 private Task EnableShellIntegrationAsync ( CancellationToken cancellationToken )
585585 {
586- // Imported on 01/03/23 from
586+ // Imported on 01/03/24 from
587587 // https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1
588588 // with quotes escaped, `__VSCodeOriginalPSConsoleHostReadLine` removed (as it's done
589589 // in our own ReadLine function), and `[Console]::Write` replaced with `Write-Host`.
@@ -602,42 +602,74 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken)
602602
603603$Global:__LastHistoryId = -1
604604
605+ # Store the nonce in script scope and unset the global
606+ $Nonce = $env:VSCODE_NONCE
607+ $env:VSCODE_NONCE = $null
608+
609+ if ($env:VSCODE_ENV_REPLACE) {
610+ $Split = $env:VSCODE_ENV_REPLACE.Split("":"")
611+ foreach ($Item in $Split) {
612+ $Inner = $Item.Split('=')
613+ [Environment]::SetEnvironmentVariable($Inner[0], $Inner[1].Replace('\x3a', ':'))
614+ }
615+ $env:VSCODE_ENV_REPLACE = $null
616+ }
617+ if ($env:VSCODE_ENV_PREPEND) {
618+ $Split = $env:VSCODE_ENV_PREPEND.Split("":"")
619+ foreach ($Item in $Split) {
620+ $Inner = $Item.Split('=')
621+ [Environment]::SetEnvironmentVariable($Inner[0], $Inner[1].Replace('\x3a', ':') + [Environment]::GetEnvironmentVariable($Inner[0]))
622+ }
623+ $env:VSCODE_ENV_PREPEND = $null
624+ }
625+ if ($env:VSCODE_ENV_APPEND) {
626+ $Split = $env:VSCODE_ENV_APPEND.Split("":"")
627+ foreach ($Item in $Split) {
628+ $Inner = $Item.Split('=')
629+ [Environment]::SetEnvironmentVariable($Inner[0], [Environment]::GetEnvironmentVariable($Inner[0]) + $Inner[1].Replace('\x3a', ':'))
630+ }
631+ $env:VSCODE_ENV_APPEND = $null
632+ }
633+
605634function Global:__VSCode-Escape-Value([string]$value) {
606635 # NOTE: In PowerShell v6.1+, this can be written `$value -replace '…', { … }` instead of `[regex]::Replace`.
607636 # Replace any non-alphanumeric characters.
608637 [regex]::Replace($value, '[\\\n;]', { param($match)
609- # Encode the (ascii) matches as `\x<hex>`
610- -Join (
611- [System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ }
612- )
613- })
638+ # Encode the (ascii) matches as `\x<hex>`
639+ -Join (
640+ [System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ }
641+ )
642+ })
614643}
615644
616645function Global:Prompt() {
646+ $FakeCode = [int]!$global:?
617647 # NOTE: We disable strict mode for the scope of this function because it unhelpfully throws an
618648 # error when $LastHistoryEntry is null, and is not otherwise useful.
619649 Set-StrictMode -Off
620- $FakeCode = [int]!$global:?
621650 $LastHistoryEntry = Get-History -Count 1
622651 # Skip finishing the command if the first command has not yet started
623652 if ($Global:__LastHistoryId -ne -1) {
624653 if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) {
625654 # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command)
626- $Result = ""$([char]0x1b)]633;E`a""
655+ $Result = ""$([char]0x1b)]633;E`a""
627656 $Result += ""$([char]0x1b)]633;D`a""
628- } else {
657+ }
658+ else {
629659 # Command finished command line
630- # OSC 633 ; A ; <CommandLine?> ST
631- $Result = ""$([char]0x1b)]633;E;""
660+ # OSC 633 ; E ; <CommandLine?> ; <Nonce ?> ST
661+ $Result = ""$([char]0x1b)]633;E;""
632662 # Sanitize the command line to ensure it can get transferred to the terminal and can be parsed
633663 # correctly. This isn't entirely safe but good for most cases, it's important for the Pt parameter
634664 # to only be composed of _printable_ characters as per the spec.
635665 if ($LastHistoryEntry.CommandLine) {
636666 $CommandLine = $LastHistoryEntry.CommandLine
637- } else {
667+ }
668+ else {
638669 $CommandLine = """"
639670 }
640671 $Result += $(__VSCode-Escape-Value $CommandLine)
672+ $Result += "";$Nonce""
641673 $Result += ""`a""
642674 # Command finished exit code
643675 # OSC 633 ; D [; <ExitCode>] ST
@@ -649,7 +681,7 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken)
649681 $Result += ""$([char]0x1b)]633;A`a""
650682 # Current working directory
651683 # OSC 633 ; <Property>=<Value> ST
652- $Result += if($pwd.Provider.Name -eq 'FileSystem'){ ""$([char]0x1b)]633;P;Cwd=$(__VSCode-Escape-Value $pwd.ProviderPath)`a""}
684+ $Result += if ($pwd.Provider.Name -eq 'FileSystem') { ""$([char]0x1b)]633;P;Cwd=$(__VSCode-Escape-Value $pwd.ProviderPath)`a"" }
653685 # Before running the original prompt, put $? back to what it was:
654686 if ($FakeCode -ne 0) {
655687 Write-Error ""failure"" -ea ignore
@@ -664,28 +696,91 @@ private Task EnableShellIntegrationAsync(CancellationToken cancellationToken)
664696
665697# Set IsWindows property
666698if ($PSVersionTable.PSVersion -lt ""6.0"") {
667- [Console]::Write(""$([char]0x1b)]633;P;IsWindows=$true`a"")
668- } else {
669- [Console]::Write(""$([char]0x1b)]633;P;IsWindows=$IsWindows`a"")
699+ # Windows PowerShell is only available on Windows
700+ Write-Host -NoNewLine ""$([char]0x1b)]633;P;IsWindows=$true`a""
701+ }
702+ else {
703+ Write-Host -NoNewLine ""$([char]0x1b)]633;P;IsWindows=$IsWindows`a""
670704}
671705
672706# Set always on key handlers which map to default VS Code keybindings
673707function Set-MappedKeyHandler {
674708 param ([string[]] $Chord, [string[]]$Sequence)
675- $Handler = $(Get-PSReadLineKeyHandler -Chord $Chord | Select-Object -First 1)
709+ try {
710+ $Handler = Get-PSReadLineKeyHandler -Chord $Chord | Select-Object -First 1
711+ }
712+ catch [System.Management.Automation.ParameterBindingException] {
713+ # PowerShell 5.1 ships with PSReadLine 2.0.0 which does not have -Chord,
714+ # so we check what's bound and filter it.
715+ $Handler = Get-PSReadLineKeyHandler -Bound | Where-Object -FilterScript { $_.Key -eq $Chord } | Select-Object -First 1
716+ }
676717 if ($Handler) {
677718 Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function
678719 }
679720}
680721
722+ $Global:__VSCodeHaltCompletions = $false
681723function Set-MappedKeyHandlers {
682724 Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a'
683725 Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b'
684726 Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c'
685727 Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d'
728+
729+ # Conditionally enable suggestions
730+ if ($env:VSCODE_SUGGEST -eq '1') {
731+ Remove-Item Env:VSCODE_SUGGEST
732+
733+ # VS Code send completions request (may override Ctrl+Spacebar)
734+ Set-PSReadLineKeyHandler -Chord 'F12,e' -ScriptBlock {
735+ Send-Completions
736+ }
737+
738+ # Suggest trigger characters
739+ Set-PSReadLineKeyHandler -Chord ""-"" -ScriptBlock {
740+ [Microsoft.PowerShell.PSConsoleReadLine]::Insert(""-"")
741+ if (!$Global:__VSCodeHaltCompletions) {
742+ Send-Completions
743+ }
744+ }
745+
746+ Set-PSReadLineKeyHandler -Chord 'F12,y' -ScriptBlock {
747+ $Global:__VSCodeHaltCompletions = $true
748+ }
749+
750+ Set-PSReadLineKeyHandler -Chord 'F12,z' -ScriptBlock {
751+ $Global:__VSCodeHaltCompletions = $false
752+ }
753+ }
754+ }
755+
756+ function Send-Completions {
757+ $commandLine = """"
758+ $cursorIndex = 0
759+ # TODO: Since fuzzy matching exists, should completions be provided only for character after the
760+ # last space and then filter on the client side? That would let you trigger ctrl+space
761+ # anywhere on a word and have full completions available
762+ [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$commandLine, [ref]$cursorIndex)
763+ $completionPrefix = $commandLine
764+
765+ # Get completions
766+ $result = ""`e]633;Completions""
767+ if ($completionPrefix.Length -gt 0) {
768+ # Get and send completions
769+ $completions = TabExpansion2 -inputScript $completionPrefix -cursorColumn $cursorIndex
770+ if ($null -ne $completions.CompletionMatches) {
771+ $result += "";$($completions.ReplacementIndex);$($completions.ReplacementLength);$($cursorIndex);""
772+ $result += $completions.CompletionMatches | ConvertTo-Json -Compress
773+ }
774+ }
775+ $result += ""`a""
776+
777+ Write-Host -NoNewLine $result
686778}
687779
688- Set-MappedKeyHandlers
780+ # Register key handlers if PSReadLine is available
781+ if (Get-Module -Name PSReadLine) {
782+ Set-MappedKeyHandlers
783+ }
689784 " ;
690785
691786 return ExecutePSCommandAsync ( new PSCommand ( ) . AddScript ( shellIntegrationScript ) , cancellationToken ) ;
0 commit comments