From 0b6ffca35d7a129bb629a192b51cdfc637b6a4a8 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Fri, 14 Jan 2022 22:54:29 +0200 Subject: [PATCH] fix(pwsh): Avoid polluting the global function namespace (#3424) * fix(pwsh): Avoid polluting the global function namespace This is an alternative to #3386 * Review changes * Move continuation prompt after setting starship env --- src/init/starship.ps1 | 257 +++++++++++++++++++++--------------------- 1 file changed, 128 insertions(+), 129 deletions(-) diff --git a/src/init/starship.ps1 b/src/init/starship.ps1 index 266ad2c6..2ae5158b 100755 --- a/src/init/starship.ps1 +++ b/src/init/starship.ps1 @@ -1,6 +1,8 @@ #!/usr/bin/env pwsh -function Get-Arguments { +# Create a new dynamic module so we don't pollute the global namespace with our functions and +# variables +$null = New-Module starship { function Get-Cwd { $cwd = Get-Location $provider_prefix = "$($cwd.Provider.ModuleName)\$($cwd.Provider.Name)::" @@ -9,7 +11,7 @@ function Get-Arguments { # NOTE: ProviderPath is only a physical filesystem path for the "FileSystem" provider # E.g. `Dev:\` -> `C:\Users\Joe Bloggs\Dev\` Path = $cwd.ProviderPath; - # Resolve the provider-logical path + # Resolve the provider-logical path # NOTE: Attempt to trim any "provider prefix" from the path string. # E.g. `Microsoft.PowerShell.Core\FileSystem::Dev:\` -> `Dev:\` LogicalPath = @@ -21,133 +23,130 @@ function Get-Arguments { } } - # @ makes sure the result is an array even if single or no values are returned - $jobs = @(Get-Job | Where-Object { $_.State -eq 'Running' }).Count - - $cwd = Get-Cwd - $arguments = @( - "prompt" - "--path=$($cwd.Path)", - "--logical-path=$($cwd.LogicalPath)", - "--terminal-width=$($Host.UI.RawUI.WindowSize.Width)", - "--jobs=$($jobs)" + function Invoke-Native { + param($Executable, $Arguments) + $startInfo = New-Object System.Diagnostics.ProcessStartInfo -ArgumentList $Executable -Property @{ + StandardOutputEncoding = [System.Text.Encoding]::UTF8; + RedirectStandardOutput = $true; + RedirectStandardError = $true; + CreateNoWindow = $true; + UseShellExecute = $false; + }; + if ($startInfo.ArgumentList.Add) { + # PowerShell 6+ uses .NET 5+ and supports the ArgumentList property + # which bypasses the need for manually escaping the argument list into + # a command string. + foreach ($arg in $Arguments) { + $startInfo.ArgumentList.Add($arg); + } + } + else { + # Build an arguments string which follows the C++ command-line argument quoting rules + # See: https://docs.microsoft.com/en-us/previous-versions//17w5ykft(v=vs.85)?redirectedfrom=MSDN + $escaped = $Arguments | ForEach-Object { + $s = $_ -Replace '(\\+)"','$1$1"'; # Escape backslash chains immediately preceding quote marks. + $s = $s -Replace '(\\+)$','$1$1'; # Escape backslash chains immediately preceding the end of the string. + $s = $s -Replace '"','\"'; # Escape quote marks. + "`"$s`"" # Quote the argument. + } + $startInfo.Arguments = $escaped -Join ' '; + } + $process = [System.Diagnostics.Process]::Start($startInfo) + + # stderr isn't displayed with this style of invocation + # Manually write it to console + $stderr = $process.StandardError.ReadToEnd().Trim() + if ($stderr -ne '') { + # Write-Error doesn't work here + $host.ui.WriteErrorLine($stderr) + } + + $process.StandardOutput.ReadToEnd(); + } + + function global:prompt { + $origDollarQuestion = $global:? + $origLastExitCode = $global:LASTEXITCODE + + # Invoke precmd, if specified + try { + if (Test-Path function:Invoke-Starship-PreCommand) { + Invoke-Starship-PreCommand + } + } catch {} + + # @ makes sure the result is an array even if single or no values are returned + $jobs = @(Get-Job | Where-Object { $_.State -eq 'Running' }).Count + + $cwd = Get-Cwd + $arguments = @( + "prompt" + "--path=$($cwd.Path)", + "--logical-path=$($cwd.LogicalPath)", + "--terminal-width=$($Host.UI.RawUI.WindowSize.Width)", + "--jobs=$($jobs)" + ) + + # Whe start from the premise that the command executed correctly, which covers also the fresh console. + $lastExitCodeForPrompt = 0 + if ($lastCmd = Get-History -Count 1) { + # In case we have a False on the Dollar hook, we know there's an error. + if (-not $origDollarQuestion) { + # We retrieve the InvocationInfo from the most recent error using $error[0] + $lastCmdletError = try { $error[0] | Where-Object { $_ -ne $null } | Select-Object -ExpandProperty InvocationInfo } catch { $null } + # We check if the last command executed matches the line that caused the last error, in which case we know + # it was an internal Powershell command, otherwise, there MUST be an error code. + $lastExitCodeForPrompt = if ($null -ne $lastCmdletError -and $lastCmd.CommandLine -eq $lastCmdletError.Line) { 1 } else { $origLastExitCode } + } + $duration = [math]::Round(($lastCmd.EndExecutionTime - $lastCmd.StartExecutionTime).TotalMilliseconds) + + $arguments += "--cmd-duration=$($duration)" + } + + $arguments += "--status=$($lastExitCodeForPrompt)" + + # Invoke Starship + Invoke-Native -Executable ::STARSHIP:: -Arguments $arguments + + # Propagate the original $LASTEXITCODE from before the prompt function was invoked. + $global:LASTEXITCODE = $origLastExitCode + + # Propagate the original $? automatic variable value from before the prompt function was invoked. + # + # $? is a read-only or constant variable so we can't directly override it. + # In order to propagate up its original boolean value we will take an action + # which will produce the desired value. + # + # This has to be the very last thing that happens in the prompt function + # since every PowerShell command sets the $? variable. + if ($global:? -ne $origDollarQuestion) { + if ($origDollarQuestion) { + # Simple command which will execute successfully and set $? = True without any other side affects. + 1+1 + } else { + # Write-Error will set $? to False. + # ErrorAction Ignore will prevent the error from being added to the $Error collection. + Write-Error '' -ErrorAction 'Ignore' + } + } + + } + + # Disable virtualenv prompt, it breaks starship + $ENV:VIRTUAL_ENV_DISABLE_PROMPT=1 + + $ENV:STARSHIP_SHELL = "powershell" + + # Set up the session key that will be used to store logs + $ENV:STARSHIP_SESSION_KEY = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | ForEach-Object { [char]$_ }) + + # Invoke Starship and set continuation prompt + Set-PSReadLineOption -ContinuationPrompt ( + Invoke-Native -Executable ::STARSHIP:: -Arguments @( + "prompt", + "--continuation" + ) ) - - # Whe start from the premise that the command executed correctly, which covers also the fresh console. - $lastExitCodeForPrompt = 0 - if ($lastCmd = Get-History -Count 1) { - # In case we have a False on the Dollar hook, we know there's an error. - if (-not $origDollarQuestion) { - # We retrieve the InvocationInfo from the most recent error using $error[0] - $lastCmdletError = try { $error[0] | Where-Object { $_ -ne $null } | Select-Object -ExpandProperty InvocationInfo } catch { $null } - # We check if the last command executed matches the line that caused the last error, in which case we know - # it was an internal Powershell command, otherwise, there MUST be an error code. - $lastExitCodeForPrompt = if ($null -ne $lastCmdletError -and $lastCmd.CommandLine -eq $lastCmdletError.Line) { 1 } else { $origLastExitCode } - } - $duration = [math]::Round(($lastCmd.EndExecutionTime - $lastCmd.StartExecutionTime).TotalMilliseconds) - - $arguments += "--cmd-duration=$($duration)" - } - $arguments += "--status=$($lastExitCodeForPrompt)" - - return $arguments + Export-ModuleMember } - -function Invoke-Native { - param($Executable, $Arguments) - $startInfo = New-Object System.Diagnostics.ProcessStartInfo -ArgumentList $Executable -Property @{ - StandardOutputEncoding = [System.Text.Encoding]::UTF8; - RedirectStandardOutput = $true; - RedirectStandardError = $true; - CreateNoWindow = $true; - UseShellExecute = $false; - }; - if ($startInfo.ArgumentList.Add) { - # PowerShell 6+ uses .NET 5+ and supports the ArgumentList property - # which bypasses the need for manually escaping the argument list into - # a command string. - foreach ($arg in $Arguments) { - $startInfo.ArgumentList.Add($arg); - } - } - else { - # Build an arguments string which follows the C++ command-line argument quoting rules - # See: https://docs.microsoft.com/en-us/previous-versions//17w5ykft(v=vs.85)?redirectedfrom=MSDN - $escaped = $Arguments | ForEach-Object { - $s = $_ -Replace '(\\+)"','$1$1"'; # Escape backslash chains immediately preceding quote marks. - $s = $s -Replace '(\\+)$','$1$1'; # Escape backslash chains immediately preceding the end of the string. - $s = $s -Replace '"','\"'; # Escape quote marks. - "`"$s`"" # Quote the argument. - } - $startInfo.Arguments = $escaped -Join ' '; - } - $process = [System.Diagnostics.Process]::Start($startInfo) - - # stderr isn't displayed with this style of invocation - # Manually write it to console - $stderr = $process.StandardError.ReadToEnd().Trim() - if ($stderr -ne '') { - # Write-Error doesn't work here - $host.ui.WriteErrorLine($stderr) - } - - $process.StandardOutput.ReadToEnd(); -} - -function global:prompt { - $origDollarQuestion = $global:? - $origLastExitCode = $global:LASTEXITCODE - - # Invoke precmd, if specified - try { - if (Test-Path function:Invoke-Starship-PreCommand) { - Invoke-Starship-PreCommand - } - } catch {} - - # Get arguments for starship prompt - $arguments = Get-Arguments - - # Invoke Starship - Invoke-Native -Executable ::STARSHIP:: -Arguments $arguments - - # Propagate the original $LASTEXITCODE from before the prompt function was invoked. - $global:LASTEXITCODE = $origLastExitCode - - # Propagate the original $? automatic variable value from before the prompt function was invoked. - # - # $? is a read-only or constant variable so we can't directly override it. - # In order to propagate up its original boolean value we will take an action - # which will produce the desired value. - # - # This has to be the very last thing that happens in the prompt function - # since every PowerShell command sets the $? variable. - if ($global:? -ne $origDollarQuestion) { - if ($origDollarQuestion) { - # Simple command which will execute successfully and set $? = True without any other side affects. - 1+1 - } else { - # Write-Error will set $? to False. - # ErrorAction Ignore will prevent the error from being added to the $Error collection. - Write-Error '' -ErrorAction 'Ignore' - } - } - -} - -# Get arguments for starship continuation prompt -$arguments = Get-Arguments -$arguments += "--continuation" - -# Invoke Starship and set continuation prompt -$continuation = Invoke-Native -Executable ::STARSHIP:: -Arguments $arguments -Set-PSReadLineOption -ContinuationPrompt $continuation - -# Disable virtualenv prompt, it breaks starship -$ENV:VIRTUAL_ENV_DISABLE_PROMPT=1 - -$ENV:STARSHIP_SHELL = "powershell" - -# Set up the session key that will be used to store logs -$ENV:STARSHIP_SESSION_KEY = -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 16 | ForEach-Object { [char]$_ })