mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-28 20:39:40 -08:00
fix(shell): quote paths properly in init scripts
This commit is contained in:
parent
2ed2c038af
commit
db8eac7c5d
|
@ -3,6 +3,7 @@ package shell
|
|||
import (
|
||||
_ "embed"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
"fmt"
|
||||
|
@ -51,25 +52,111 @@ func getExecutablePath(env environment.Environment) (string, error) {
|
|||
// On Windows, it fails when the excutable is called in MSYS2 for example
|
||||
// which uses unix style paths to resolve the executable's location.
|
||||
// PowerShell knows how to resolve both, so we can swap this without any issue.
|
||||
executable = strings.ReplaceAll(executable, "\\", "/")
|
||||
switch env.Flags().Shell {
|
||||
case BASH, ZSH:
|
||||
executable = strings.ReplaceAll(executable, " ", "\\ ")
|
||||
executable = strings.ReplaceAll(executable, "(", "\\(")
|
||||
executable = strings.ReplaceAll(executable, ")", "\\)")
|
||||
if runtime.GOOS == environment.WINDOWS {
|
||||
executable = strings.ReplaceAll(executable, "\\", "/")
|
||||
}
|
||||
return executable, nil
|
||||
}
|
||||
|
||||
func Init(env environment.Environment) string {
|
||||
executable, err := getExecutablePath(env)
|
||||
if err != nil {
|
||||
return noExe
|
||||
func quotePwshStr(str string) string {
|
||||
return fmt.Sprintf("'%s'", strings.ReplaceAll(str, "'", "''"))
|
||||
}
|
||||
|
||||
func quotePosixStr(str string) string {
|
||||
if len(str) == 0 {
|
||||
return "''"
|
||||
}
|
||||
needQuoting := false
|
||||
var b strings.Builder
|
||||
for _, r := range str {
|
||||
normal := false
|
||||
switch r {
|
||||
case '!', ';', '"', '(', ')', '[', ']', '{', '}', '$', '|', '&', '>', '<', '`', ' ', '#', '~', '*', '?', '=':
|
||||
b.WriteRune(r)
|
||||
case '\\', '\'':
|
||||
b.WriteByte('\\')
|
||||
b.WriteRune(r)
|
||||
case '\a':
|
||||
b.WriteString(`\a`)
|
||||
case '\b':
|
||||
b.WriteString(`\b`)
|
||||
case '\f':
|
||||
b.WriteString(`\f`)
|
||||
case '\n':
|
||||
b.WriteString(`\n`)
|
||||
case '\r':
|
||||
b.WriteString(`\r`)
|
||||
case '\t':
|
||||
b.WriteString(`\t`)
|
||||
case '\v':
|
||||
b.WriteString(`\v`)
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
normal = true
|
||||
}
|
||||
if !normal {
|
||||
needQuoting = true
|
||||
}
|
||||
}
|
||||
// the quoting form $'...' is used for a string includes any special characters
|
||||
if needQuoting {
|
||||
return fmt.Sprintf("$'%s'", b.String())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func quoteFishStr(str string) string {
|
||||
if len(str) == 0 {
|
||||
return "''"
|
||||
}
|
||||
needQuoting := false
|
||||
var b strings.Builder
|
||||
for _, r := range str {
|
||||
normal := false
|
||||
switch r {
|
||||
case ';', '"', '(', ')', '[', ']', '{', '}', '$', '|', '&', '>', '<', ' ', '#', '~', '*', '?', '=':
|
||||
b.WriteRune(r)
|
||||
case '\\', '\'':
|
||||
b.WriteByte('\\')
|
||||
b.WriteRune(r)
|
||||
default:
|
||||
b.WriteRune(r)
|
||||
normal = true
|
||||
}
|
||||
if !normal {
|
||||
needQuoting = true
|
||||
}
|
||||
}
|
||||
// single quotes are used when the string includes any special characters
|
||||
if needQuoting {
|
||||
return fmt.Sprintf("'%s'", b.String())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func quoteLuaStr(str string) string {
|
||||
if len(str) == 0 {
|
||||
return "''"
|
||||
}
|
||||
return fmt.Sprintf("'%s'", strings.NewReplacer(`\`, `\\`, `'`, `\'`).Replace(str))
|
||||
}
|
||||
|
||||
func quoteNuStr(str string) string {
|
||||
if len(str) == 0 {
|
||||
return "''"
|
||||
}
|
||||
return fmt.Sprintf(`"%s"`, strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace(str))
|
||||
}
|
||||
|
||||
func Init(env environment.Environment) string {
|
||||
shell := env.Flags().Shell
|
||||
switch shell {
|
||||
case PWSH, PWSH5:
|
||||
return fmt.Sprintf("(@(&\"%s\" init %s --config=\"%s\" --print) -join \"`n\") | Invoke-Expression", executable, shell, env.Flags().Config)
|
||||
executable, err := getExecutablePath(env)
|
||||
if err != nil {
|
||||
return noExe
|
||||
}
|
||||
return fmt.Sprintf("(@(& %s init %s --config=%s --print) -join \"`n\") | Invoke-Expression", quotePwshStr(executable), shell, quotePwshStr(env.Flags().Config))
|
||||
case ZSH, BASH, FISH, CMD:
|
||||
return PrintInit(env)
|
||||
case NU:
|
||||
|
@ -87,32 +174,43 @@ func PrintInit(env environment.Environment) string {
|
|||
}
|
||||
shell := env.Flags().Shell
|
||||
configFile := env.Flags().Config
|
||||
var script string
|
||||
switch shell {
|
||||
case PWSH, PWSH5:
|
||||
script := getShellInitScript(executable, configFile, pwshInit)
|
||||
return strings.ReplaceAll(script, "::SHELL::", shell)
|
||||
executable = quotePwshStr(executable)
|
||||
configFile = quotePwshStr(configFile)
|
||||
script = pwshInit
|
||||
case ZSH:
|
||||
return getShellInitScript(executable, configFile, zshInit)
|
||||
executable = quotePosixStr(executable)
|
||||
configFile = quotePosixStr(configFile)
|
||||
script = zshInit
|
||||
case BASH:
|
||||
return getShellInitScript(executable, configFile, bashInit)
|
||||
executable = quotePosixStr(executable)
|
||||
configFile = quotePosixStr(configFile)
|
||||
script = bashInit
|
||||
case FISH:
|
||||
return getShellInitScript(executable, configFile, fishInit)
|
||||
executable = quoteFishStr(executable)
|
||||
configFile = quoteFishStr(configFile)
|
||||
script = fishInit
|
||||
case CMD:
|
||||
return getShellInitScript(executable, configFile, cmdInit)
|
||||
executable = quoteLuaStr(executable)
|
||||
configFile = quoteLuaStr(configFile)
|
||||
script = cmdInit
|
||||
case NU:
|
||||
return getShellInitScript(executable, configFile, nuInit)
|
||||
executable = quoteNuStr(executable)
|
||||
configFile = quoteNuStr(configFile)
|
||||
script = nuInit
|
||||
default:
|
||||
return fmt.Sprintf("echo \"No initialization script available for %s\"", shell)
|
||||
}
|
||||
}
|
||||
|
||||
func getShellInitScript(executable, configFile, script string) string {
|
||||
script = strings.ReplaceAll(script, "::OMP::", executable)
|
||||
script = strings.ReplaceAll(script, "::CONFIG::", configFile)
|
||||
script = strings.ReplaceAll(script, "::TRANSIENT::", strconv.FormatBool(Transient))
|
||||
script = strings.ReplaceAll(script, "::ERROR_LINE::", strconv.FormatBool(ErrorLine))
|
||||
script = strings.ReplaceAll(script, "::TOOLTIPS::", strconv.FormatBool(Tooltips))
|
||||
return script
|
||||
return strings.NewReplacer(
|
||||
"::OMP::", executable,
|
||||
"::CONFIG::", configFile,
|
||||
"::SHELL::", shell,
|
||||
"::TRANSIENT::", strconv.FormatBool(Transient),
|
||||
"::ERROR_LINE::", strconv.FormatBool(ErrorLine),
|
||||
"::TOOLTIPS::", strconv.FormatBool(Tooltips),
|
||||
).Replace(script)
|
||||
}
|
||||
|
||||
func createNuInit(env environment.Environment) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package shell
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"oh-my-posh/environment"
|
||||
"oh-my-posh/mock"
|
||||
"testing"
|
||||
|
@ -29,3 +30,83 @@ func TestConsoleBackgroundColorTemplate(t *testing.T) {
|
|||
assert.Equal(t, tc.Expected, color, tc.Case)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuotePwshStr(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
expected string
|
||||
}{
|
||||
{str: ``, expected: `''`},
|
||||
{str: `/tmp/oh-my-posh`, expected: `'/tmp/oh-my-posh'`},
|
||||
{str: `/tmp/omp's dir/oh-my-posh`, expected: `'/tmp/omp''s dir/oh-my-posh'`},
|
||||
{str: `C:\tmp\oh-my-posh.exe`, expected: `'C:\tmp\oh-my-posh.exe'`},
|
||||
{str: `C:\tmp\omp's dir\oh-my-posh.exe`, expected: `'C:\tmp\omp''s dir\oh-my-posh.exe'`},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
assert.Equal(t, tc.expected, quotePwshStr(tc.str), fmt.Sprintf("quotePwshStr: %s", tc.str))
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuotePosixStr(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
expected string
|
||||
}{
|
||||
{str: ``, expected: `''`},
|
||||
{str: `/tmp/oh-my-posh`, expected: `/tmp/oh-my-posh`},
|
||||
{str: `/tmp/omp's dir/oh-my-posh`, expected: `$'/tmp/omp\'s dir/oh-my-posh'`},
|
||||
{str: `C:/tmp/oh-my-posh.exe`, expected: `C:/tmp/oh-my-posh.exe`},
|
||||
{str: `C:/tmp/omp's dir/oh-my-posh.exe`, expected: `$'C:/tmp/omp\'s dir/oh-my-posh.exe'`},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
assert.Equal(t, tc.expected, quotePosixStr(tc.str), fmt.Sprintf("quotePosixStr: %s", tc.str))
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteFishStr(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
expected string
|
||||
}{
|
||||
{str: ``, expected: `''`},
|
||||
{str: `/tmp/oh-my-posh`, expected: `/tmp/oh-my-posh`},
|
||||
{str: `/tmp/omp's dir/oh-my-posh`, expected: `'/tmp/omp\'s dir/oh-my-posh'`},
|
||||
{str: `C:/tmp/oh-my-posh.exe`, expected: `C:/tmp/oh-my-posh.exe`},
|
||||
{str: `C:/tmp/omp's dir/oh-my-posh.exe`, expected: `'C:/tmp/omp\'s dir/oh-my-posh.exe'`},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
assert.Equal(t, tc.expected, quoteFishStr(tc.str), fmt.Sprintf("quoteFishStr: %s", tc.str))
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteLuaStr(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
expected string
|
||||
}{
|
||||
{str: ``, expected: `''`},
|
||||
{str: `/tmp/oh-my-posh`, expected: `'/tmp/oh-my-posh'`},
|
||||
{str: `/tmp/omp's dir/oh-my-posh`, expected: `'/tmp/omp\'s dir/oh-my-posh'`},
|
||||
{str: `C:/tmp/oh-my-posh.exe`, expected: `'C:/tmp/oh-my-posh.exe'`},
|
||||
{str: `C:/tmp/omp's dir/oh-my-posh.exe`, expected: `'C:/tmp/omp\'s dir/oh-my-posh.exe'`},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
assert.Equal(t, tc.expected, quoteLuaStr(tc.str), fmt.Sprintf("quoteLuaStr: %s", tc.str))
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuoteNuStr(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
expected string
|
||||
}{
|
||||
{str: ``, expected: `''`},
|
||||
{str: `/tmp/oh-my-posh`, expected: `"/tmp/oh-my-posh"`},
|
||||
{str: `/tmp/omp's dir/oh-my-posh`, expected: `"/tmp/omp's dir/oh-my-posh"`},
|
||||
{str: `C:/tmp/oh-my-posh.exe`, expected: `"C:/tmp/oh-my-posh.exe"`},
|
||||
{str: `C:/tmp/omp's dir/oh-my-posh.exe`, expected: `"C:/tmp/omp's dir/oh-my-posh.exe"`},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
assert.Equal(t, tc.expected, quoteNuStr(tc.str), fmt.Sprintf("quoteNuStr: %s", tc.str))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export POSH_THEME='::CONFIG::'
|
||||
export POSH_THEME=::CONFIG::
|
||||
export POWERLINE_COMMAND="oh-my-posh"
|
||||
export CONDA_PROMPT_MODIFIER=false
|
||||
|
||||
|
@ -10,15 +10,18 @@ if [[ ! -d "/tmp" ]]; then
|
|||
fi
|
||||
|
||||
# start timer on command start
|
||||
PS0='$(::OMP:: get millis > "$TIMER_START")'
|
||||
PS0='$(_omp_start_timer)'
|
||||
# set secondary prompt
|
||||
PS2="$(::OMP:: print secondary --config="$POSH_THEME" --shell=bash --shell-version="$BASH_VERSION")"
|
||||
|
||||
function _omp_start_timer() {
|
||||
::OMP:: get millis > "$TIMER_START"
|
||||
}
|
||||
|
||||
function _omp_hook() {
|
||||
local ret=$?
|
||||
|
||||
omp_stack_count=$((${#DIRSTACK[@]} - 1))
|
||||
omp_elapsed=-1
|
||||
local omp_stack_count=$((${#DIRSTACK[@]} - 1))
|
||||
local omp_elapsed=-1
|
||||
if [[ -f "$TIMER_START" ]]; then
|
||||
omp_now=$(::OMP:: get millis)
|
||||
omp_start_time=$(cat "$TIMER_START")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
set --export POSH_THEME '::CONFIG::'
|
||||
set --export POSH_THEME ::CONFIG::
|
||||
set --global POWERLINE_COMMAND "oh-my-posh"
|
||||
set --global CONDA_PROMPT_MODIFIER false
|
||||
set --global omp_tooltip_command ""
|
||||
|
@ -11,7 +11,7 @@ function fish_prompt
|
|||
# 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 --error $omp_status_cache --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION
|
||||
::OMP:: print transient --config $POSH_THEME --shell fish --error $omp_status_cache --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION
|
||||
return
|
||||
end
|
||||
set --global omp_status_cache $omp_status_cache_temp
|
||||
|
@ -29,7 +29,7 @@ function fish_prompt
|
|||
set --global --export omp_last_status_generation $status_generation
|
||||
end
|
||||
|
||||
'::OMP::' print primary --config $POSH_THEME --shell fish --error $omp_status_cache --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION
|
||||
::OMP:: print primary --config $POSH_THEME --shell fish --error $omp_status_cache --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION
|
||||
end
|
||||
|
||||
function fish_right_prompt
|
||||
|
@ -39,14 +39,14 @@ function fish_right_prompt
|
|||
return
|
||||
end
|
||||
if test -n "$omp_tooltip_command"
|
||||
set omp_tooltip_prompt ('::OMP::' print tooltip --config $POSH_THEME --shell fish --error $omp_status_cache --shell-version $FISH_VERSION --command $omp_tooltip_command)
|
||||
set omp_tooltip_prompt (::OMP:: print tooltip --config $POSH_THEME --shell fish --error $omp_status_cache --shell-version $FISH_VERSION --command $omp_tooltip_command)
|
||||
if test -n "$omp_tooltip_prompt"
|
||||
echo -n $omp_tooltip_prompt
|
||||
set omp_tooltip_command ""
|
||||
return
|
||||
end
|
||||
end
|
||||
'::OMP::' print right --config $POSH_THEME --shell fish --error $omp_status_cache --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION
|
||||
::OMP:: print right --config $POSH_THEME --shell fish --error $omp_status_cache --execution-time $omp_duration --stack-count $omp_stack_count --shell-version $FISH_VERSION
|
||||
end
|
||||
|
||||
function postexec_omp --on-event fish_postexec
|
||||
|
|
|
@ -27,14 +27,14 @@ local tooltip_active = false
|
|||
local cached_prompt = {}
|
||||
|
||||
local function omp_exe()
|
||||
return [["::OMP::"]]
|
||||
return '"'..::OMP::..'"'
|
||||
end
|
||||
|
||||
local function omp_config()
|
||||
return [["::CONFIG::"]]
|
||||
return '"'..::CONFIG::..'"'
|
||||
end
|
||||
|
||||
os.setenv("POSH_THEME", omp_config())
|
||||
os.setenv("POSH_THEME", ::CONFIG::)
|
||||
|
||||
local function can_async()
|
||||
if (clink.version_encoded or 0) >= 10030001 then
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
let-env POWERLINE_COMMAND = 'oh-my-posh'
|
||||
let-env POSH_THEME = '::CONFIG::'
|
||||
let-env POSH_THEME = ::CONFIG::
|
||||
let-env PROMPT_INDICATOR = ""
|
||||
# By default displays the right prompt on the first line
|
||||
# making it annoying when you have a multiline prompt
|
||||
|
@ -8,9 +8,9 @@ let-env PROMPT_COMMAND_RIGHT = {''}
|
|||
let-env NU_VERSION = (version | get version)
|
||||
|
||||
# PROMPTS
|
||||
let-env PROMPT_MULTILINE_INDICATOR = (^'::OMP::' print secondary $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.NU_VERSION)")
|
||||
let-env PROMPT_MULTILINE_INDICATOR = (^::OMP:: print secondary $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.NU_VERSION)")
|
||||
|
||||
let-env PROMPT_COMMAND = {
|
||||
let width = (term size -c | get columns | into string)
|
||||
^'::OMP::' print primary $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.NU_VERSION)" $"--execution-time=($env.CMD_DURATION_MS)" $"--error=($env.LAST_EXIT_CODE)" $"--terminal-width=($width)"
|
||||
^::OMP:: print primary $"--config=($env.POSH_THEME)" --shell=nu $"--shell-version=($env.NU_VERSION)" $"--execution-time=($env.CMD_DURATION_MS)" $"--error=($env.LAST_EXIT_CODE)" $"--terminal-width=($width)"
|
||||
}
|
||||
|
|
|
@ -15,14 +15,14 @@ function global:Get-PoshStackCount {
|
|||
|
||||
New-Module -Name "oh-my-posh-core" -ScriptBlock {
|
||||
$script:ErrorCode = 0
|
||||
$script:OMPExecutable = "::OMP::"
|
||||
$script:OMPExecutable = ::OMP::
|
||||
$script:ShellName = "::SHELL::"
|
||||
$script:PSVersion = $PSVersionTable.PSVersion.ToString()
|
||||
$script:TransientPrompt = $false
|
||||
$env:POWERLINE_COMMAND = "oh-my-posh"
|
||||
$env:CONDA_PROMPT_MODIFIER = $false
|
||||
if (("::CONFIG::" -ne '') -and (Test-Path "::CONFIG::")) {
|
||||
$env:POSH_THEME = (Resolve-Path -Path "::CONFIG::").ProviderPath
|
||||
if ((::CONFIG:: -ne '') -and (Test-Path ::CONFIG::)) {
|
||||
$env:POSH_THEME = (Resolve-Path -Path ::CONFIG::).ProviderPath
|
||||
}
|
||||
# specific module support (disabled by default)
|
||||
if ($null -eq $env:POSH_GIT_ENABLED) {
|
||||
|
@ -247,7 +247,7 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
|
|||
|___/
|
||||
'@
|
||||
Write-Host $logo
|
||||
$themes = Get-ChildItem -Path "$Path\*" -Include '*.omp.json' | Sort-Object Name
|
||||
$themes = Get-ChildItem -Path "$Path/*" -Include '*.omp.json' | Sort-Object Name
|
||||
if ($List -eq $true) {
|
||||
$themes | Select-Object @{ Name = 'hyperlink'; Expression = { Get-FileHyperlink -uri $_.FullName } } | Format-Table -HideTableHeaders
|
||||
} else {
|
||||
|
@ -263,7 +263,7 @@ Themes location: $(Get-FileHyperlink -uri "$Path")
|
|||
|
||||
To change your theme, adjust the init script in $PROFILE.
|
||||
Example:
|
||||
oh-my-posh init pwsh --config $Path/jandedobbeleer.omp.json | Invoke-Expression
|
||||
oh-my-posh init pwsh --config '$((Join-Path $Path "jandedobbeleer.omp.json") -replace "'", "''")' | Invoke-Expression
|
||||
|
||||
"@
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export POSH_THEME='::CONFIG::'
|
||||
export POSH_THEME=::CONFIG::
|
||||
export POWERLINE_COMMAND="oh-my-posh"
|
||||
export CONDA_PROMPT_MODIFIER=false
|
||||
|
||||
|
@ -64,7 +64,7 @@ if [[ "::TOOLTIPS::" = "true" ]]; then
|
|||
zle -N self-insert
|
||||
fi
|
||||
|
||||
_posh-zle-line-init() {
|
||||
function _posh-zle-line-init() {
|
||||
[[ $CONTEXT == start ]] || return 0
|
||||
|
||||
# Start regular line editor
|
||||
|
|
Loading…
Reference in a new issue