@@ -77,6 +77,8 @@ internal class PsesInternalHost : PSHost, IHostSupportsInteractiveSession, IRuns
77
77
78
78
private string _localComputerName ;
79
79
80
+ private bool _shellIntegrationEnabled ;
81
+
80
82
private ConsoleKeyInfo ? _lastKey ;
81
83
82
84
private bool _skipNextPrompt ;
@@ -254,6 +256,18 @@ public async Task<bool> TryStartAsync(HostStartOptions startOptions, Cancellatio
254
256
_logger . LogDebug ( "Profiles loaded!" ) ;
255
257
}
256
258
259
+ if ( startOptions . ShellIntegrationEnabled )
260
+ {
261
+ _logger . LogDebug ( "Enabling shell integration..." ) ;
262
+ _shellIntegrationEnabled = true ;
263
+ await EnableShellIntegrationAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
264
+ _logger . LogDebug ( "Shell integration enabled!" ) ;
265
+ }
266
+ else
267
+ {
268
+ _logger . LogDebug ( "Shell integration not enabled!" ) ;
269
+ }
270
+
257
271
if ( startOptions . InitialWorkingDirectory is not null )
258
272
{
259
273
_logger . LogDebug ( $ "Setting InitialWorkingDirectory to { startOptions . InitialWorkingDirectory } ...") ;
@@ -487,6 +501,95 @@ internal Task LoadHostProfilesAsync(CancellationToken cancellationToken)
487
501
cancellationToken ) ;
488
502
}
489
503
504
+ private Task EnableShellIntegrationAsync ( CancellationToken cancellationToken )
505
+ {
506
+ // Imported on 11/17/22 from
507
+ // https://github.com/microsoft/vscode/blob/main/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1
508
+ // with quotes escaped, `__VSCodeOriginalPSConsoleHostReadLine` removed (as it's done
509
+ // in our own ReadLine function), and `[Console]::Write` replaced with `Write-Host`.
510
+ const string shellIntegrationScript = @"
511
+ # Prevent installing more than once per session
512
+ if (Test-Path variable:global:__VSCodeOriginalPrompt) {
513
+ return;
514
+ }
515
+
516
+ # Disable shell integration when the language mode is restricted
517
+ if ($ExecutionContext.SessionState.LanguageMode -ne ""FullLanguage"") {
518
+ return;
519
+ }
520
+
521
+ $Global:__VSCodeOriginalPrompt = $function:Prompt
522
+
523
+ $Global:__LastHistoryId = -1
524
+
525
+
526
+ function Global:Prompt() {
527
+ $FakeCode = [int]!$global:?
528
+ $LastHistoryEntry = Get-History -Count 1
529
+ # Skip finishing the command if the first command has not yet started
530
+ if ($Global:__LastHistoryId -ne -1) {
531
+ if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) {
532
+ # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command)
533
+ $Result = ""`e]633;E`a""
534
+ $Result += ""`e]633;D`a""
535
+ } else {
536
+ # Command finished command line
537
+ # OSC 633 ; A ; <CommandLine?> ST
538
+ $Result = ""`e]633;E;""
539
+ # Sanitize the command line to ensure it can get transferred to the terminal and can be parsed
540
+ # correctly. This isn't entirely safe but good for most cases, it's important for the Pt parameter
541
+ # to only be composed of _printable_ characters as per the spec.
542
+ if ($LastHistoryEntry.CommandLine) {
543
+ $CommandLine = $LastHistoryEntry.CommandLine
544
+ } else {
545
+ $CommandLine = """"
546
+ }
547
+ $Result += $CommandLine.Replace(""\"", ""\\"").Replace(""`n"", ""\x0a"").Replace("";"", ""\x3b"")
548
+ $Result += ""`a""
549
+ # Command finished exit code
550
+ # OSC 633 ; D [; <ExitCode>] ST
551
+ $Result += ""`e]633;D;$FakeCode`a""
552
+ }
553
+ }
554
+ # Prompt started
555
+ # OSC 633 ; A ST
556
+ $Result += ""`e]633;A`a""
557
+ # Current working directory
558
+ # OSC 633 ; <Property>=<Value> ST
559
+ $Result += if($pwd.Provider.Name -eq 'FileSystem'){""`e]633;P;Cwd=$($pwd.ProviderPath)`a""}
560
+ # Before running the original prompt, put $? back to what it was:
561
+ if ($FakeCode -ne 0) { Write-Error ""failure"" -ea ignore }
562
+ # Run the original prompt
563
+ $Result += $Global:__VSCodeOriginalPrompt.Invoke()
564
+ # Write command started
565
+ $Result += ""`e]633;B`a""
566
+ $Global:__LastHistoryId = $LastHistoryEntry.Id
567
+ return $Result
568
+ }
569
+
570
+ # Set IsWindows property
571
+ Write-Host -NoNewLine ""`e]633;P;IsWindows=$($IsWindows)`a""
572
+
573
+ # Set always on key handlers which map to default VS Code keybindings
574
+ function Set-MappedKeyHandler {
575
+ param ([string[]] $Chord, [string[]]$Sequence)
576
+ $Handler = $(Get-PSReadLineKeyHandler -Chord $Chord | Select-Object -First 1)
577
+ if ($Handler) {
578
+ Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function
579
+ }
580
+ }
581
+ function Set-MappedKeyHandlers {
582
+ Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a'
583
+ Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b'
584
+ Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c'
585
+ Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d'
586
+ }
587
+ Set-MappedKeyHandlers
588
+ " ;
589
+
590
+ return ExecutePSCommandAsync ( new PSCommand ( ) . AddScript ( shellIntegrationScript ) , cancellationToken ) ;
591
+ }
592
+
490
593
public Task SetInitialWorkingDirectoryAsync ( string path , CancellationToken cancellationToken )
491
594
{
492
595
return Directory . Exists ( path )
@@ -962,8 +1065,23 @@ private string InvokeReadLine(CancellationToken cancellationToken)
962
1065
private void InvokeInput ( string input , CancellationToken cancellationToken )
963
1066
{
964
1067
SetBusy ( true ) ;
1068
+
965
1069
try
966
1070
{
1071
+ if ( _shellIntegrationEnabled )
1072
+ {
1073
+ InvokePSCommand (
1074
+ new PSCommand ( ) . AddScript ( "Write-Host -NoNewLine \" `e]633;C`a\" " ) ,
1075
+ new PowerShellExecutionOptions
1076
+ {
1077
+ AddToHistory = false ,
1078
+ ThrowOnError = false ,
1079
+ WriteOutputToHost = true ,
1080
+ FromRepl = true ,
1081
+ } ,
1082
+ cancellationToken ) ;
1083
+ }
1084
+
967
1085
InvokePSCommand (
968
1086
new PSCommand ( ) . AddScript ( input , useLocalScope : false ) ,
969
1087
new PowerShellExecutionOptions
0 commit comments