From 66f1347357cf439137b2a7811a889d89e2b2f4c8 Mon Sep 17 00:00:00 2001 From: "L. Yeung" Date: Wed, 10 Jul 2024 20:28:08 +0800 Subject: [PATCH] fix(shell): avoid unnecessary CLI calls for prompt repaint --- src/prompt/engine.go | 8 +- src/prompt/tooltip.go | 52 ++++----- src/shell/scripts/omp.fish | 208 +++++++++++++++++++++-------------- src/shell/scripts/omp.ps1 | 219 ++++++++++++++++++------------------- src/shell/scripts/omp.zsh | 39 +++---- 5 files changed, 280 insertions(+), 246 deletions(-) diff --git a/src/prompt/engine.go b/src/prompt/engine.go index afdc892e..c98b92df 100644 --- a/src/prompt/engine.go +++ b/src/prompt/engine.go @@ -172,11 +172,11 @@ func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool { defer e.patchPowerShellBleed() // This is deprecated but we leave it in to not break configs - // It is encouraged to used "newline": true on block level - // rather than the standalone the linebreak block + // It is encouraged to use "newline": true on block level + // rather than the standalone linebreak block if block.Type == config.LineBreak { // do not print a newline to avoid a leading space - // when we're printin the first primary prompt in + // when we're printing the first primary prompt in // the shell if !cancelNewline { e.writeNewline() @@ -191,7 +191,7 @@ func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool { } // do not print a newline to avoid a leading space - // when we're printin the first primary prompt in + // when we're printing the first primary prompt in // the shell if block.Newline && !cancelNewline { e.writeNewline() diff --git a/src/prompt/tooltip.go b/src/prompt/tooltip.go index 23955365..45ea0134 100644 --- a/src/prompt/tooltip.go +++ b/src/prompt/tooltip.go @@ -1,6 +1,7 @@ package prompt import ( + "slices" "strings" "github.com/jandedobbeleer/oh-my-posh/src/config" @@ -9,6 +10,18 @@ import ( ) func (e *Engine) Tooltip(tip string) string { + supportedShells := []string{ + shell.ZSH, + shell.CMD, + shell.FISH, + shell.PWSH, + shell.PWSH5, + shell.GENERIC, + } + if !slices.Contains(supportedShells, e.Env.Shell()) { + return "" + } + tip = strings.Trim(tip, " ") tooltips := make([]*config.Segment, 0, 1) @@ -35,40 +48,29 @@ func (e *Engine) Tooltip(tip string) string { Alignment: config.Right, Segments: tooltips, } + block.Init(e.Env) + if !block.Enabled() { + return "" + } + text, length := e.renderBlockSegments(block) switch e.Env.Shell() { - case shell.ZSH, shell.CMD, shell.FISH, shell.GENERIC: - block.Init(e.Env) - if !block.Enabled() { - return "" - } - text, _ := e.renderBlockSegments(block) - return text case shell.PWSH, shell.PWSH5: - if !block.Enabled() { + e.rprompt = text + e.rpromptLength = length + e.currentLineLength = e.Env.Flags().Column + space, ok := e.canWriteRightBlock(true) + if !ok { return "" } - - consoleWidth, err := e.Env.TerminalWidth() - if err != nil || consoleWidth == 0 { - return "" - } - - text, length := e.renderBlockSegments(block) - - space := consoleWidth - e.Env.Flags().Column - length - if space <= 0 { - return "" - } - // clear from cursor to the end of the line in case a previous tooltip - // is cut off and partially preserved, if the new one is shorter - e.write(terminal.ClearAfter()) + e.write(terminal.SaveCursorPosition()) e.write(strings.Repeat(" ", space)) e.write(text) + e.write(terminal.RestoreCursorPosition()) return e.string() + default: + return text } - - return "" } func (e *Engine) shouldInvokeWithTip(segment *config.Segment, tip string) bool { diff --git a/src/shell/scripts/omp.fish b/src/shell/scripts/omp.fish index 41903f34..20a7078b 100644 --- a/src/shell/scripts/omp.fish +++ b/src/shell/scripts/omp.fish @@ -1,17 +1,22 @@ set --export POSH_THEME ::CONFIG:: set --export POSH_SHELL_VERSION $FISH_VERSION -set --global POWERLINE_COMMAND "oh-my-posh" +set --global POWERLINE_COMMAND oh-my-posh set --global POSH_PID $fish_pid set --global CONDA_PROMPT_MODIFIER false -set --global omp_tooltip_prompt "" -set --global has_omp_tooltip false -set --global omp_transient 0 +set --global omp_tooltip_command '' +set --global omp_current_rprompt '' +set --global omp_transient false + +# We use this to avoid unnecessary CLI calls for prompt repaint. +set --global omp_new_prompt true # template function for context loading function set_poshcontext - return + return end +# NOTE: Input function calls via `commandline --function` are put into a queue and will not be executed until an outer regular function returns. See https://fishshell.com/docs/current/cmds/commandline.html. + function fish_prompt set --local omp_status_cache_temp $status set --local omp_pipestatus_cache_temp $pipestatus @@ -19,11 +24,14 @@ function fish_prompt # commandline --function repaint does not do this # see https://github.com/fish-shell/fish-shell/issues/8418 printf \e\[0J - if test "$omp_transient" = "1" - ::OMP:: print transient --config $POSH_THEME --shell fish --status $omp_status_cache --pipestatus="$omp_pipestatus_cache" --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION --no-status=$omp_no_exit_code - return + if test "$omp_transient" = true + ::OMP:: print transient --config $POSH_THEME --shell fish --status $omp_status_cache --pipestatus="$omp_pipestatus_cache" --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION --no-status=$omp_no_exit_code + return + end + if test "$omp_new_prompt" = false + echo -n "$omp_current_prompt" + return end - set --global omp_status_cache $omp_status_cache_temp set --global omp_pipestatus_cache $omp_pipestatus_cache_temp set --global omp_stack_count (count $dirstack) @@ -31,22 +39,22 @@ function fish_prompt set --global omp_no_exit_code false # check if variable set, < 3.2 case - if set --query omp_lastcommand; and test "$omp_lastcommand" = "" - set omp_duration 0 - set omp_no_exit_code true + if set --query omp_lastcommand; and test -z "$omp_lastcommand" + set omp_duration 0 + set omp_no_exit_code true end # works with fish >=3.2 if set --query omp_last_status_generation; and test "$omp_last_status_generation" = "$status_generation" - set omp_duration 0 - set omp_no_exit_code true + set omp_duration 0 + set omp_no_exit_code true else if test -z "$omp_last_status_generation" - # first execution - $status_generation is 0, $omp_last_status_generation is empty - set omp_no_exit_code true + # first execution - $status_generation is 0, $omp_last_status_generation is empty + set omp_no_exit_code true end if set --query status_generation - set --global --export omp_last_status_generation $status_generation + set --global omp_last_status_generation $status_generation end set_poshcontext @@ -55,112 +63,146 @@ function fish_prompt set --local omp_cleared false set --local last_command (history search --max 1) - if test "$last_command" = "clear" - set omp_cleared true + if test "$last_command" = clear + set omp_cleared true end ::PROMPT_MARK:: - ::OMP:: print primary --config $POSH_THEME --shell fish --status $omp_status_cache --pipestatus="$omp_pipestatus_cache" --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION --cleared=$omp_cleared --no-status=$omp_no_exit_code + # The prompt is saved for possible reuse, typically a repaint after clearing the screen buffer. + set --global omp_current_prompt (::OMP:: print primary --config $POSH_THEME --shell fish --status $omp_status_cache --pipestatus="$omp_pipestatus_cache" --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION --cleared=$omp_cleared --no-status=$omp_no_exit_code | string collect) + echo -n "$omp_current_prompt" end function fish_right_prompt - if test "$omp_transient" = "1" - echo -n "" - set omp_transient 0 - set has_omp_tooltip false - return + if test "$omp_transient" = true + set omp_transient false + return end - if test -n "$omp_tooltip_prompt" - echo -n $omp_tooltip_prompt - set omp_tooltip_prompt "" - set has_omp_tooltip true - return + # Repaint an existing right prompt. + if test "$omp_new_prompt" = false + echo -n "$omp_current_rprompt" + return end - set has_omp_tooltip false - ::OMP:: print right --config $POSH_THEME --shell fish --status $omp_status_cache --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION + set omp_new_prompt false + set --global omp_current_rprompt (::OMP:: print right --config $POSH_THEME --shell fish --status $omp_status_cache --pipestatus="$omp_pipestatus_cache" --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION --no-status=$omp_no_exit_code | string join '') + echo -n "$omp_current_rprompt" end function postexec_omp --on-event fish_postexec - # works with fish <3.2 - # pre and postexec not fired for empty command in fish >=3.2 - set --global --export omp_lastcommand $argv -end - -# fix tooltip not resetting on SIGINT (ctrl+c) -function sigint_omp --on-signal INT - commandline --function repaint + # works with fish <3.2 + # pre and postexec not fired for empty command in fish >=3.2 + set --global omp_lastcommand $argv end function preexec_omp --on-event fish_preexec - if "::FTCS_MARKS::" = "true" - echo -ne "\e]133;C\a" - end + if test "::FTCS_MARKS::" = true + echo -ne "\e]133;C\a" + end end # perform cleanup so a new initialization in current session works -if test "$(string match -e '_render_transient' $(bind \r --user 2>/dev/null))" != '' - bind -e \r +if test -n (bind \r --user 2>/dev/null | string match -e _omp_enter_key_handler) + bind -e \r -M default + bind -e \r -M insert + bind -e \r -M visual end -if test "$(string match -e '_render_tooltip' $(bind \x20 --user 2>/dev/null))" != '' - bind -e \x20 +if test -n (bind \n --user 2>/dev/null | string match -e _omp_enter_key_handler) + bind -e \n -M default + bind -e \n -M insert + bind -e \n -M visual +end +if test -n (bind \cc --user 2>/dev/null | string match -e _omp_ctrl_c_key_handler) + bind -e \cc -M default + bind -e \cc -M insert + bind -e \cc -M visual +end +if test -n (bind \x20 --user 2>/dev/null | string match -e _omp_space_key_handler) + bind -e \x20 -M default + bind -e \x20 -M insert end # tooltip -function _render_tooltip - commandline --function expand-abbr - commandline --insert " " - # get the first word of command line as tip - set omp_tooltip_command (commandline --current-buffer | string trim -l | string split --allow-empty -f1 ' ' | string collect) - if not test -n "$omp_tooltip_command" - return - end - set omp_tooltip_prompt (::OMP:: print tooltip --config $POSH_THEME --shell fish --status $omp_status_cache --shell-version $FISH_VERSION --command $omp_tooltip_command) - if not test -n "$omp_tooltip_prompt" - if test "$has_omp_tooltip" = "true" - commandline --function repaint +function _omp_space_key_handler + commandline --function expand-abbr + commandline --insert ' ' + # Get the first word of command line as tip. + set --local tooltip_command (commandline --current-buffer | string trim -l | string split --allow-empty -f1 ' ' | string collect) + # Ignore an empty/repeated tooltip command. + if test -z "$tooltip_command" || test "$tooltip_command" = "$omp_tooltip_command" + return end - return - end - commandline --function repaint + set omp_tooltip_command $tooltip_command + set --local tooltip_prompt (::OMP:: print tooltip --config $POSH_THEME --shell fish --status $omp_status_cache --pipestatus="$omp_pipestatus_cache" --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION --command $omp_tooltip_command --no-status=$omp_no_exit_code | string join '') + if test -z "$tooltip_prompt" + return + end + # Save the tooltip prompt to avoid unnecessary CLI calls. + set omp_current_rprompt $tooltip_prompt + commandline --function repaint end -if test "::TOOLTIPS::" = "true" - bind \x20 _render_tooltip -M default - bind \x20 _render_tooltip -M insert +if test "::TOOLTIPS::" = true + bind \x20 _omp_space_key_handler -M default + bind \x20 _omp_space_key_handler -M insert end # transient prompt -function _render_transient - if commandline --paging-mode - commandline --function accept-autosuggestion - return - end - set omp_transient 1 - commandline --function repaint - commandline --function execute +function _omp_enter_key_handler + if commandline --paging-mode + commandline --function accept-autosuggestion + return + end + if commandline --is-valid || test -z (commandline --current-buffer | string trim -l | string collect) + set omp_new_prompt true + set omp_tooltip_command '' + if test "::TRANSIENT::" = true + set omp_transient true + commandline --function repaint + end + end + commandline --function execute end -if test "::TRANSIENT::" = "true" - bind \r _render_transient -M default - bind \r _render_transient -M insert - bind \r _render_transient -M visual +function _omp_ctrl_c_key_handler + if test -z (commandline --current-buffer | string collect) + return + end + # Render a transient prompt on Ctrl-C with non-empty command line buffer. + set omp_new_prompt true + set omp_tooltip_command '' + if test "::TRANSIENT::" = true + set omp_transient true + commandline --function repaint + end + commandline --function cancel-commandline + commandline --function repaint end +bind \r _omp_enter_key_handler -M default +bind \r _omp_enter_key_handler -M insert +bind \r _omp_enter_key_handler -M visual +bind \n _omp_enter_key_handler -M default +bind \n _omp_enter_key_handler -M insert +bind \n _omp_enter_key_handler -M visual +bind \cc _omp_ctrl_c_key_handler -M default +bind \cc _omp_ctrl_c_key_handler -M insert +bind \cc _omp_ctrl_c_key_handler -M visual + # legacy functions function enable_poshtooltips - return + return end function enable_poshtransientprompt - return + return end -if test "::UPGRADE::" = "true" - echo "::UPGRADENOTICE::" +if test "::UPGRADE::" = true + echo "::UPGRADENOTICE::" end -if test "::AUTOUPGRADE::" = "true" - ::OMP:: upgrade +if test "::AUTOUPGRADE::" = true + ::OMP:: upgrade end diff --git a/src/shell/scripts/omp.ps1 b/src/shell/scripts/omp.ps1 index b6b02ec4..057f2628 100644 --- a/src/shell/scripts/omp.ps1 +++ b/src/shell/scripts/omp.ps1 @@ -14,20 +14,29 @@ function global:Get-PoshStackCount { } New-Module -Name "oh-my-posh-core" -ScriptBlock { + # Check `ConstrainedLanguage` mode. + $script:ConstrainedLanguageMode = $ExecutionContext.SessionState.LanguageMode -eq "ConstrainedLanguage" + + # Prompt related backup. + $script:OriginalPromptFunction = $Function:prompt + $script:OriginalContinuationPrompt = (Get-PSReadLineOption).ContinuationPrompt + $script:OriginalPromptText = (Get-PSReadLineOption).PromptText + + $script:NoExitCode = $true $script:ErrorCode = 0 $script:ExecutionTime = 0 $script:OMPExecutable = ::OMP:: $script:ShellName = "::SHELL::" $script:PSVersion = $PSVersionTable.PSVersion.ToString() $script:TransientPrompt = $false - $script:ToolTipCommand = "" + $script:TooltipCommand = '' $env:POWERLINE_COMMAND = "oh-my-posh" $env:POSH_SHELL_VERSION = $script:PSVersion $env:POSH_PID = $PID $env:CONDA_PROMPT_MODIFIER = $false # set the default theme - if ((::CONFIG:: -ne '') -and (Test-Path -LiteralPath ::CONFIG::)) { + if (::CONFIG:: -and (Test-Path -LiteralPath ::CONFIG::)) { $env:POSH_THEME = (Resolve-Path -Path ::CONFIG::).ProviderPath } @@ -56,7 +65,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { [string[]]$Arguments = @() ) - if ($ExecutionContext.SessionState.LanguageMode -eq "ConstrainedLanguage") { + if ($script:ConstrainedLanguageMode) { $standardOut = Invoke-Expression "& `$FileName `$Arguments 2>&1" $standardOut -join "`n" return @@ -99,12 +108,14 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { } $StartInfo.CreateNoWindow = $true [void]$Process.Start() - # we do this to remove a deadlock potential on Windows + + # Remove deadlock potential on Windows. $stdoutTask = $Process.StandardOutput.ReadToEndAsync() $stderrTask = $Process.StandardError.ReadToEndAsync() + $Process.WaitForExit() $stderr = $stderrTask.Result.Trim() - if ($stderr -ne '') { + if ($stderr) { $Host.UI.WriteErrorLine($stderr) } $stdoutTask.Result @@ -120,77 +131,79 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { return $pswd } - if (("::TOOLTIPS::" -eq "true") -and ($ExecutionContext.SessionState.LanguageMode -ne "ConstrainedLanguage")) { - Set-PSReadLineKeyHandler -Key Spacebar -BriefDescription 'OhMyPoshSpaceKeyHandler' -ScriptBlock { - [Microsoft.PowerShell.PSConsoleReadLine]::Insert(' ') - $command = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$null) - $command = $command.TrimStart().Split(" ", 2) | Select-Object -First 1 - # ignore an empty/repeated tip - if ($command -eq '' -or $command -eq $script:ToolTipCommand) { - return - } - $position = $host.UI.RawUI.CursorPosition - $terminalWidth = $Host.UI.RawUI.WindowSize.Width - $cleanPSWD = Get-CleanPSWD - $standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", "tooltip", "--status=$script:ErrorCode", "--shell=$script:ShellName", "--pswd=$cleanPSWD", "--config=$env:POSH_THEME", "--command=$command", "--shell-version=$script:PSVersion", "--column=$($position.X)", "--terminal-width=$terminalWidth")) - # ignore an empty tooltip - if ($standardOut -eq '') { - return - } - Write-Host $standardOut -NoNewline - $host.UI.RawUI.CursorPosition = $position - # cache the tip command - $script:ToolTipCommand = $command - # we need this workaround to prevent the text after cursor from disappearing when the tooltip is rendered - [Microsoft.PowerShell.PSConsoleReadLine]::Insert(' ') - [Microsoft.PowerShell.PSConsoleReadLine]::Undo() + function Get-TerminalWidth { + $terminalWidth = $Host.UI.RawUI.WindowSize.Width + # Set a sane default when the value can't be retrieved. + if (-not $terminalWidth) { + return 0 } + $terminalWidth } - function Set-TransientPrompt { - $previousOutputEncoding = [Console]::OutputEncoding - $executingCommand = $false + if (!$script:ConstrainedLanguageMode) { + if ('::TOOLTIPS::' -eq 'true') { + Set-PSReadLineKeyHandler -Key Spacebar -BriefDescription 'OhMyPoshSpaceKeyHandler' -ScriptBlock { + param([ConsoleKeyInfo]$key) + [Microsoft.PowerShell.PSConsoleReadLine]::SelfInsert($key) + try { + $command = '' + [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$null) + # Get the first word of command line as tip. + $command = $command.TrimStart().Split(' ', 2) | Select-Object -First 1 + # Ignore an empty/repeated tooltip command. + if (!$command -or ($command -eq $script:TooltipCommand)) { + return + } + $script:TooltipCommand = $command + $column = $Host.UI.RawUI.CursorPosition.X + $terminalWidth = Get-TerminalWidth + $cleanPSWD = Get-CleanPSWD + $stackCount = global:Get-PoshStackCount + $standardOut = (Start-Utf8Process $script:OMPExecutable @("print", "tooltip", "--status=$script:ErrorCode", "--shell=$script:ShellName", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--command=$command", "--shell-version=$script:PSVersion", "--column=$column", "--terminal-width=$terminalWidth", "--no-status=$script:NoExitCode")) -join '' + if (!$standardOut) { + return + } + Write-Host $standardOut -NoNewline - try { - $parseErrors = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$null, [ref]$null, [ref]$parseErrors, [ref]$null) - if ($parseErrors.Count -eq 0) { - $executingCommand = $true + # Workaround to prevent the text after cursor from disappearing when the tooltip is printed. + [Microsoft.PowerShell.PSConsoleReadLine]::Insert(' ') + [Microsoft.PowerShell.PSConsoleReadLine]::Undo() + } + finally {} + } + } + + function Set-TransientPrompt { + $previousOutputEncoding = [Console]::OutputEncoding + try { $script:TransientPrompt = $true [Console]::OutputEncoding = [Text.Encoding]::UTF8 [Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt() } - } - finally { - # If PSReadline is set to display suggestion list, this workaround is needed to clear the buffer below - # before accepting the current commandline. The max amount of items in the list is 10, so 12 lines - # are cleared (10 + 1 more for the prompt + 1 more for current commandline). - if ((Get-PSReadLineOption).PredictionViewStyle -eq 'ListView') { - $terminalHeight = $Host.UI.RawUI.WindowSize.Height - # only do this on an valid value - if ([int]$terminalHeight -gt 0) { - [Microsoft.PowerShell.PSConsoleReadLine]::Insert("`n" * [System.Math]::Min($terminalHeight - $Host.UI.RawUI.CursorPosition.Y - 1, 12)) - [Microsoft.PowerShell.PSConsoleReadLine]::Undo() - } + finally { + [Console]::OutputEncoding = $previousOutputEncoding } - [Console]::OutputEncoding = $previousOutputEncoding } - return $executingCommand - } - - if (("::TRANSIENT::" -eq "true") -and ($ExecutionContext.SessionState.LanguageMode -ne "ConstrainedLanguage")) { Set-PSReadLineKeyHandler -Key Enter -BriefDescription 'OhMyPoshEnterKeyHandler' -ScriptBlock { try { - $executingCommand = Set-TransientPrompt + $parseErrors = $null + [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$null, [ref]$null, [ref]$parseErrors, [ref]$null) + $executingCommand = $parseErrors.Count -eq 0 + if ($executingCommand) { + $script:TooltipCommand = '' + if ('::TRANSIENT::' -eq 'true') { + Set-TransientPrompt + } + } + } + finally { [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine() - # Write FTCS_COMMAND_EXECUTED after accepting the input - it should still happen before execution - if (("::FTCS_MARKS::" -eq "true") -and $executingCommand) { + if (('::FTCS_MARKS::' -eq 'true') -and $executingCommand) { + # Write FTCS_COMMAND_EXECUTED after accepting the input - it should still happen before execution Write-Host "$([char]0x1b)]133;C`a" -NoNewline } } - finally {} } Set-PSReadLineKeyHandler -Key Ctrl+c -BriefDescription 'OhMyPoshCtrlCKeyHandler' -ScriptBlock { try { @@ -198,37 +211,21 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { [Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$start, [ref]$null) # only render a transient prompt when no text is selected if ($start -eq -1) { - Set-TransientPrompt + $script:TooltipCommand = '' + if ('::TRANSIENT::' -eq 'true') { + Set-TransientPrompt + } } } - finally {} - - [Microsoft.PowerShell.PSConsoleReadLine]::CopyOrCancelLine() - } - } - - if (("::FTCS_MARKS::" -eq "true") -and ("::TRANSIENT::" -ne "true") -and ($ExecutionContext.SessionState.LanguageMode -ne "ConstrainedLanguage")) { - Set-PSReadLineKeyHandler -Key Enter -BriefDescription 'OhMyPoshEnterKeyHandler' -ScriptBlock { - $executingCommand = $false - try { - $parseErrors = $null - [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$null, [ref]$null, [ref]$parseErrors, [ref]$null) - $executingCommand = $parseErrors.Count -eq 0 - } - finally {} - - [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine() - - # Write FTCS_COMMAND_EXECUTED after accepting the input - it should still happen before execution - if ($executingCommand) { - Write-Host "$([char]0x1b)]133;C`a" -NoNewline + finally { + [Microsoft.PowerShell.PSConsoleReadLine]::CopyOrCancelLine() } } } if ("::ERROR_LINE::" -eq "true") { - $validLine = @(Start-Utf8Process $script:OMPExecutable @("print", "valid", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n" - $errorLine = @(Start-Utf8Process $script:OMPExecutable @("print", "error", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n" + $validLine = (Start-Utf8Process $script:OMPExecutable @("print", "valid", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n" + $errorLine = (Start-Utf8Process $script:OMPExecutable @("print", "error", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n" Set-PSReadLineOption -PromptText $validLine, $errorLine } @@ -267,9 +264,9 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { $Format = 'json' ) - $configString = @(Start-Utf8Process $script:OMPExecutable @("config", "export", "--config=$env:POSH_THEME", "--format=$Format")) + $configString = Start-Utf8Process $script:OMPExecutable @("config", "export", "--config=$env:POSH_THEME", "--format=$Format") # if no path, copy to clipboard by default - if ('' -ne $FilePath) { + if ($FilePath) { # https://stackoverflow.com/questions/3038337/powershell-resolve-path-that-might-not-exist $FilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath) [IO.File]::WriteAllLines($FilePath, $configString) @@ -288,7 +285,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { [string]$name ) $esc = [char]27 - if ("" -eq $name) { + if (!$name) { # if name not set, uri is used as the name of the hyperlink $name = $uri } @@ -335,7 +332,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock { $cleanPSWD = Get-CleanPSWD $themes | ForEach-Object -Process { Write-Host "Theme: $(Get-FileHyperlink -uri $_.FullName -Name ($_.BaseName -replace '\.omp$', ''))`n" - @(Start-Utf8Process $script:OMPExecutable @("print", "primary", "--config=$($_.FullName)", "--pswd=$cleanPSWD", "--shell=$script:ShellName")) + Start-Utf8Process $script:OMPExecutable @("print", "primary", "--config=$($_.FullName)", "--pswd=$cleanPSWD", "--shell=$script:ShellName") Write-Host "`n" } } @@ -400,7 +397,7 @@ Example: } } - function prompt { + $promptFunction = { # store the orignal last command execution status if ($global:NVS_ORIGINAL_LASTEXECUTIONSTATUS -is [bool]) { # make it compatible with NVS auto-switching, if enabled @@ -419,41 +416,30 @@ Example: $cleanPSWD = Get-CleanPSWD $stackCount = global:Get-PoshStackCount Set-PoshContext - $terminalWidth = $Host.UI.RawUI.WindowSize.Width - # set a sane default when the value can't be retrieved - if (-not $terminalWidth) { - $terminalWidth = 0 - } - - # in some cases we have an empty $script:NoExitCode - # this is a workaround to make sure we always have a value - # see https://github.com/JanDeDobbeleer/oh-my-posh/issues/4128 - if ($null -eq $script:NoExitCode) { - $script:NoExitCode = $true - } + $terminalWidth = Get-TerminalWidth # set the cursor positions, they are zero based so align with other platforms $env:POSH_CURSOR_LINE = $Host.UI.RawUI.CursorPosition.Y + 1 $env:POSH_CURSOR_COLUMN = $Host.UI.RawUI.CursorPosition.X + 1 - $standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", $script:PromptType, "--status=$script:ErrorCode", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--shell-version=$script:PSVersion", "--terminal-width=$terminalWidth", "--shell=$script:ShellName", "--no-status=$script:NoExitCode")) + $standardOut = Start-Utf8Process $script:OMPExecutable @("print", $script:PromptType, "--status=$script:ErrorCode", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--shell-version=$script:PSVersion", "--terminal-width=$terminalWidth", "--shell=$script:ShellName", "--no-status=$script:NoExitCode") # make sure PSReadLine knows if we have a multiline prompt Set-PSReadLineOption -ExtraPromptLineCount (($standardOut | Measure-Object -Line).Lines - 1) - # the output can be multiline, joining these ensures proper rendering by adding line breaks with `n + + # The output can be multi-line, joining them ensures proper rendering. $standardOut -join "`n" # remove any posh-git status $env:POSH_GIT_STATUS = $null - # remove cached tip command - $script:ToolTipCommand = "" - # restore the orignal last exit code $global:LASTEXITCODE = $script:OriginalLastExitCode } + $Function:prompt = $promptFunction + # set secondary prompt - Set-PSReadLineOption -ContinuationPrompt (@(Start-Utf8Process $script:OMPExecutable @("print", "secondary", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n") + Set-PSReadLineOption -ContinuationPrompt ((Start-Utf8Process $script:OMPExecutable @("print", "secondary", "--config=$env:POSH_THEME", "--shell=$script:ShellName")) -join "`n") # legacy functions function Enable-PoshTooltips {} @@ -461,21 +447,25 @@ Example: function Enable-PoshLineError {} # perform cleanup on removal so a new initialization in current session works - if ($ExecutionContext.SessionState.LanguageMode -ne "ConstrainedLanguage") { + if (!$script:ConstrainedLanguageMode) { $ExecutionContext.SessionState.Module.OnRemove += { - if ((Get-PSReadLineKeyHandler -Key Spacebar).Function -eq 'OhMyPoshSpaceKeyHandler') { - Remove-PSReadLineKeyHandler -Key Spacebar + Remove-Item Function:Get-PoshStackCount + $Function:prompt = $script:OriginalPromptFunction + (Get-PSReadLineOption).ContinuationPrompt = $script:OriginalContinuationPrompt + (Get-PSReadLineOption).PromptText = $script:OriginalPromptText + if ((Get-PSReadLineKeyHandler Spacebar).Function -eq 'OhMyPoshSpaceKeyHandler') { + Remove-PSReadLineKeyHandler Spacebar } - if ((Get-PSReadLineKeyHandler -Key Enter).Function -eq 'OhMyPoshEnterKeyHandler') { - Set-PSReadLineKeyHandler -Key Enter -Function AcceptLine + if ((Get-PSReadLineKeyHandler Enter).Function -eq 'OhMyPoshEnterKeyHandler') { + Set-PSReadLineKeyHandler Enter -Function AcceptLine } - if ((Get-PSReadLineKeyHandler -Key Ctrl+c).Function -eq 'OhMyPoshCtrlCKeyHandler') { - Set-PSReadLineKeyHandler -Key Ctrl+c -Function CopyOrCancelLine + if ((Get-PSReadLineKeyHandler Ctrl+c).Function -eq 'OhMyPoshCtrlCKeyHandler') { + Set-PSReadLineKeyHandler Ctrl+c -Function CopyOrCancelLine } } } - $notice = @(Start-Utf8Process $script:OMPExecutable @("notice")) + $notice = Start-Utf8Process $script:OMPExecutable @("notice") if ($notice) { Write-Host $notice -NoNewline } @@ -488,7 +478,6 @@ Example: "Export-PoshTheme" "Get-PoshThemes" "Start-Utf8Process" - "prompt" ) } | Import-Module -Global diff --git a/src/shell/scripts/omp.zsh b/src/shell/scripts/omp.zsh index a8fc1f05..222ccde9 100644 --- a/src/shell/scripts/omp.zsh +++ b/src/shell/scripts/omp.zsh @@ -20,7 +20,7 @@ function _set_posh_cursor_position() { stty raw -echo min 0 local pos - echo -en "\033[6n" > /dev/tty + echo -en "\033[6n" >/dev/tty read -r -d R pos pos=${pos:2} # strip off the esc-[ local parts=(${(s:;:)pos}) @@ -44,24 +44,24 @@ function prompt_ohmyposh_preexec() { } function prompt_ohmyposh_precmd() { - omp_last_error=$? - local pipeStatus=(${pipestatus[@]}) + omp_status_cache=$? + omp_pipestatus_cache=(${pipestatus[@]}) omp_stack_count=${#dirstack[@]} omp_elapsed=-1 - no_exit_code="true" + omp_no_exit_code="true" if [ $omp_start_time ]; then local omp_now=$(::OMP:: get millis --shell=zsh) - omp_elapsed=$(($omp_now-$omp_start_time)) - no_exit_code="false" + omp_elapsed=$(($omp_now - $omp_start_time)) + omp_no_exit_code="false" fi - if [[ "${pipeStatus[-1]}" != "$omp_last_error" ]]; then - pipeStatus=("$omp_last_error") + if [[ "${omp_pipestatus_cache[-1]}" != "$omp_status_cache" ]]; then + omp_pipestatus_cache=("$omp_status_cache") fi - count=$((POSH_PROMPT_COUNT+1)) + count=$((POSH_PROMPT_COUNT + 1)) export POSH_PROMPT_COUNT=$count set_poshcontext _set_posh_cursor_position - eval "$(::OMP:: print primary --config="$POSH_THEME" --status="$omp_last_error" --pipestatus="${pipeStatus[*]}" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$no_exit_code")" + eval "$(::OMP:: print primary --config="$POSH_THEME" --status="$omp_status_cache" --pipestatus="${omp_pipestatus_cache[*]}" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$omp_no_exit_code")" unset omp_start_time } @@ -82,7 +82,7 @@ function _posh-tooltip() { # https://github.com/zsh-users/zsh-autosuggestions - clear suggestion to avoid keeping it after the newly inserted space if [[ "$(zle -lL autosuggest-clear)" ]]; then # only if suggestions not disabled (variable not set) - if ! [[ -v _ZSH_AUTOSUGGEST_DISABLED ]]; then + if [[ ! -v _ZSH_AUTOSUGGEST_DISABLED ]]; then zle autosuggest-clear fi fi @@ -90,19 +90,19 @@ function _posh-tooltip() { # https://github.com/zsh-users/zsh-autosuggestions - fetch new suggestion after the space if [[ "$(zle -lL autosuggest-fetch)" ]]; then # only if suggestions not disabled (variable not set) - if ! [[ -v _ZSH_AUTOSUGGEST_DISABLED ]]; then + if [[ ! -v _ZSH_AUTOSUGGEST_DISABLED ]]; then zle autosuggest-fetch fi fi - # get the first word of command line as tip - local tip=${${(MS)BUFFER##[[:graph:]]*}%%[[:space:]]*} - # ignore an empty tip - if [[ -z "$tip" ]]; then + # Get the first word of command line as tip. + local tooltip_command=${${(MS)BUFFER##[[:graph:]]*}%%[[:space:]]*} + # Ignore an empty/repeated tooltip command. + if [[ -z "$tooltip_command" ]] || [[ "$tooltip_command" = "$omp_tooltip_command" ]]; then return fi - local tooltip=$(::OMP:: print tooltip --config="$POSH_THEME" --shell=zsh --status="$omp_last_error" --command="$tip" --shell-version="$ZSH_VERSION") - # ignore an empty tooltip + omp_tooltip_command="$tooltip_command" + local tooltip=$(::OMP:: print tooltip --config="$POSH_THEME" --status="$omp_status_cache" --pipestatus="${omp_pipestatus_cache[*]}" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --command="$tooltip_command" --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$omp_no_exit_code") if [[ -z "$tooltip" ]]; then return fi @@ -119,7 +119,8 @@ function _posh-zle-line-init() { local -i ret=$? (( $+zle_bracketed_paste )) && print -r -n - $zle_bracketed_paste[2] - eval "$(::OMP:: print transient --status="$omp_last_error" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --config="$POSH_THEME" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$no_exit_code")" + omp_tooltip_command='' + eval "$(::OMP:: print transient --config="$POSH_THEME" --status="$omp_status_cache" --pipestatus="${omp_pipestatus_cache[*]}" --execution-time="$omp_elapsed" --stack-count="$omp_stack_count" --eval --shell=zsh --shell-version="$ZSH_VERSION" --no-status="$omp_no_exit_code")" zle .reset-prompt # Exit the shell if we receive EOT.