shell: fix zsh color formatting for PS1 in environments (#39497)

* shell: fix zsh color formatting for PS1 in environments

The `colorize` function in `llnl.util.tty.color` only applies proper formatting for Bash
ANSI and for console output, but this is not what zsh expects for environment variables.

In particular, when using `zsh`, `spack env activate -p` produces a `PS1` prompt that
looks like this:

```
\[\033[0;92m\][ENVIRONMENT]\[\033[0m\]
```

For zsh the formatting should be:

```
\e[0;92m[ENVIRONMENT]\e0;m
```

- [x] Add a `zsh` option to `colorize()` to enable zsh color formatting
- [x] Add conditional to choose the right `PS1` for `zsh`, `bash`, and `sh`
- [x] Don't use color escapes for `sh`, as they don't print properly

* convert lots of += lines to triple quotes
This commit is contained in:
Todd Gamblin 2023-12-28 15:36:30 -08:00 committed by GitHub
parent fc1e0178bf
commit 379eeda576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 15 deletions

View File

@ -204,17 +204,23 @@ def color_when(value):
class match_to_ansi: class match_to_ansi:
def __init__(self, color=True, enclose=False): def __init__(self, color=True, enclose=False, zsh=False):
self.color = _color_when_value(color) self.color = _color_when_value(color)
self.enclose = enclose self.enclose = enclose
self.zsh = zsh
def escape(self, s): def escape(self, s):
"""Returns a TTY escape sequence for a color""" """Returns a TTY escape sequence for a color"""
if self.color: if self.color:
if self.enclose: if self.zsh:
return r"\[\033[%sm\]" % s result = rf"\e[0;{s}m"
else: else:
return "\033[%sm" % s result = f"\033[{s}m"
if self.enclose:
result = rf"\[{result}\]"
return result
else: else:
return "" return ""
@ -261,9 +267,11 @@ def colorize(string, **kwargs):
codes, for output to non-console devices. codes, for output to non-console devices.
enclose (bool): If True, enclose ansi color sequences with enclose (bool): If True, enclose ansi color sequences with
square brackets to prevent misestimation of terminal width. square brackets to prevent misestimation of terminal width.
zsh (bool): If True, use zsh ansi codes instead of bash ones (for variables like PS1)
""" """
color = _color_when_value(kwargs.get("color", get_color_when())) color = _color_when_value(kwargs.get("color", get_color_when()))
string = re.sub(color_re, match_to_ansi(color, kwargs.get("enclose")), string) zsh = kwargs.get("zsh", False)
string = re.sub(color_re, match_to_ansi(color, kwargs.get("enclose")), string, zsh)
string = string.replace("}}", "}") string = string.replace("}}", "}")
return string return string

View File

@ -3,6 +3,7 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import os import os
import textwrap
from typing import Optional from typing import Optional
import llnl.util.tty as tty import llnl.util.tty as tty
@ -54,22 +55,35 @@ def activate_header(env, shell, prompt=None, view: Optional[str] = None):
if view: if view:
cmds += "$Env:SPACK_ENV_VIEW='%s'\n" % view cmds += "$Env:SPACK_ENV_VIEW='%s'\n" % view
else: else:
if "color" in os.getenv("TERM", "") and prompt: bash_color_prompt = colorize(f"@G{{{prompt}}}", color=True, enclose=True)
prompt = colorize("@G{%s}" % prompt, color=True, enclose=True) zsh_color_prompt = colorize(f"@G{{{prompt}}}", color=True, enclose=False, zsh=True)
cmds += "export SPACK_ENV=%s;\n" % env.path cmds += "export SPACK_ENV=%s;\n" % env.path
if view: if view:
cmds += "export SPACK_ENV_VIEW=%s;\n" % view cmds += "export SPACK_ENV_VIEW=%s;\n" % view
cmds += "alias despacktivate='spack env deactivate';\n" cmds += "alias despacktivate='spack env deactivate';\n"
if prompt: if prompt:
cmds += "if [ -z ${SPACK_OLD_PS1+x} ]; then\n" cmds += textwrap.dedent(
cmds += " if [ -z ${PS1+x} ]; then\n" rf"""
cmds += " PS1='$$$$';\n" if [ -z ${{SPACK_OLD_PS1+x}} ]; then
cmds += " fi;\n" if [ -z ${{PS1+x}} ]; then
cmds += ' export SPACK_OLD_PS1="${PS1}";\n' PS1='$$$$';
cmds += "fi;\n" fi;
cmds += 'export PS1="%s ${PS1}";\n' % prompt export SPACK_OLD_PS1="${{PS1}}";
fi;
if [ -n "${{TERM:-}}" ] && [ "${{TERM#*color}}" != "${{TERM}}" ] && \
[ -n "${{BASH:-}}" ];
then
export PS1="{bash_color_prompt} ${{PS1}}";
elif [ -n "${{TERM:-}}" ] && [ "${{TERM#*color}}" != "${{TERM}}" ] && \
[ -n "${{ZSH_NAME:-}}" ];
then
export PS1="{zsh_color_prompt} ${{PS1}}";
else
export PS1="{prompt} ${{PS1}}";
fi
"""
).lstrip("\n")
return cmds return cmds