Emulate a right prompt in bash (#2148)

* Allow passing args from render() to hl() and hlstyle() functions

* New escape arg in ShellRenderer's hlstyle() to enable/disable escaping

* Fix invalid escape sequence

* Emulate a right prompt in bash

Fixes #2103

* Document the new hl_args argument
This commit is contained in:
Cédric Schieli 2021-01-06 10:20:15 +01:00 committed by GitHub
parent 999c84a5cb
commit c31f83f831
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 147 additions and 47 deletions

View File

@ -251,7 +251,7 @@ class Renderer(object):
for line in range(theme.get_line_number() - 1, 0, -1): for line in range(theme.get_line_number() - 1, 0, -1):
yield self.render(side=None, line=line, **kwargs) yield self.render(side=None, line=line, **kwargs)
def render(self, mode=None, width=None, side=None, line=0, output_raw=False, output_width=False, segment_info=None, matcher_info=None): def render(self, mode=None, width=None, side=None, line=0, output_raw=False, output_width=False, segment_info=None, matcher_info=None, hl_args=None):
'''Render all segments. '''Render all segments.
When a width is provided, low-priority segments are dropped one at When a width is provided, low-priority segments are dropped one at
@ -286,6 +286,11 @@ class Renderer(object):
:param matcher_info: :param matcher_info:
Matcher information. Is processed in :py:meth:`get_segment_info` Matcher information. Is processed in :py:meth:`get_segment_info`
method. method.
:param dict hl_args:
Additional arguments to pass on the :py:meth:`hl` and
:py:meth`hlstyle` methods. They are ignored in the default
implementation, but renderer-specific overrides can make use of
them as run-time "configuration" information.
''' '''
theme = self.get_theme(matcher_info) theme = self.get_theme(matcher_info)
return self.do_render( return self.do_render(
@ -297,6 +302,7 @@ class Renderer(object):
output_width=output_width, output_width=output_width,
segment_info=self.get_segment_info(segment_info, mode), segment_info=self.get_segment_info(segment_info, mode),
theme=theme, theme=theme,
hl_args=hl_args
) )
def compute_divider_widths(self, theme): def compute_divider_widths(self, theme):
@ -324,7 +330,7 @@ class Renderer(object):
:return: Results of joining these segments. :return: Results of joining these segments.
''' '''
def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme): def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme, hl_args):
'''Like Renderer.render(), but accept theme in place of matcher_info '''Like Renderer.render(), but accept theme in place of matcher_info
''' '''
segments = list(theme.get_segments(side, line, segment_info, mode)) segments = list(theme.get_segments(side, line, segment_info, mode))
@ -333,14 +339,16 @@ class Renderer(object):
self._prepare_segments(segments, output_width or width) self._prepare_segments(segments, output_width or width)
hl_args = hl_args or dict()
if not width: if not width:
# No width specified, so we dont need to crop or pad anything # No width specified, so we dont need to crop or pad anything
if output_width: if output_width:
current_width = self._render_length(theme, segments, self.compute_divider_widths(theme)) current_width = self._render_length(theme, segments, self.compute_divider_widths(theme))
return construct_returned_value(self.hl_join([ return construct_returned_value(self.hl_join([
segment['_rendered_hl'] segment['_rendered_hl']
for segment in self._render_segments(theme, segments) for segment in self._render_segments(theme, segments, hl_args)
]) + self.hlstyle(), segments, current_width, output_raw, output_width) ]) + self.hlstyle(**hl_args), segments, current_width, output_raw, output_width)
divider_widths = self.compute_divider_widths(theme) divider_widths = self.compute_divider_widths(theme)
@ -394,10 +402,10 @@ class Renderer(object):
rendered_highlighted = self.hl_join([ rendered_highlighted = self.hl_join([
segment['_rendered_hl'] segment['_rendered_hl']
for segment in self._render_segments(theme, segments) for segment in self._render_segments(theme, segments, hl_args)
]) ])
if rendered_highlighted: if rendered_highlighted:
rendered_highlighted += self.hlstyle() rendered_highlighted += self.hlstyle(**hl_args)
return construct_returned_value(rendered_highlighted, segments, current_width, output_raw, output_width) return construct_returned_value(rendered_highlighted, segments, current_width, output_raw, output_width)
@ -470,7 +478,7 @@ class Renderer(object):
ret += segment_len ret += segment_len
return ret return ret
def _render_segments(self, theme, segments, render_highlighted=True): def _render_segments(self, theme, segments, hl_args, render_highlighted=True):
'''Internal segment rendering method. '''Internal segment rendering method.
This method loops through the segment array and compares the This method loops through the segment array and compares the
@ -527,6 +535,10 @@ class Renderer(object):
contents_highlighted = '' contents_highlighted = ''
draw_divider = segment['draw_' + divider_type + '_divider'] draw_divider = segment['draw_' + divider_type + '_divider']
segment_hl_args = {}
segment_hl_args.update(segment['highlight'])
segment_hl_args.update(hl_args)
# XXX Make sure self.hl() calls are called in the same order # XXX Make sure self.hl() calls are called in the same order
# segments are displayed. This is needed for Vim renderer to work. # segments are displayed. This is needed for Vim renderer to work.
if draw_divider: if draw_divider:
@ -546,14 +558,14 @@ class Renderer(object):
if side == 'left': if side == 'left':
if render_highlighted: if render_highlighted:
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args)
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args)
segment['_rendered_raw'] = contents_raw + divider_raw segment['_rendered_raw'] = contents_raw + divider_raw
segment['_rendered_hl'] = contents_highlighted + divider_highlighted segment['_rendered_hl'] = contents_highlighted + divider_highlighted
else: else:
if render_highlighted: if render_highlighted:
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False) divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args)
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args)
segment['_rendered_raw'] = divider_raw + contents_raw segment['_rendered_raw'] = divider_raw + contents_raw
segment['_rendered_hl'] = divider_highlighted + contents_highlighted segment['_rendered_hl'] = divider_highlighted + contents_highlighted
else: else:
@ -562,7 +574,7 @@ class Renderer(object):
else: else:
contents_raw = contents_raw + outer_padding contents_raw = contents_raw + outer_padding
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight']) contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args)
segment['_rendered_raw'] = contents_raw segment['_rendered_raw'] = contents_raw
segment['_rendered_hl'] = contents_highlighted segment['_rendered_hl'] = contents_highlighted
prev_segment = segment prev_segment = segment
@ -576,7 +588,7 @@ class Renderer(object):
''' '''
return string.translate(self.character_translations) return string.translate(self.character_translations)
def hlstyle(fg=None, bg=None, attrs=None): def hlstyle(fg=None, bg=None, attrs=None, **kwargs):
'''Output highlight style string. '''Output highlight style string.
Assuming highlighted string looks like ``{style}{contents}`` this method Assuming highlighted string looks like ``{style}{contents}`` this method
@ -585,10 +597,10 @@ class Renderer(object):
''' '''
raise NotImplementedError raise NotImplementedError
def hl(self, contents, fg=None, bg=None, attrs=None): def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
'''Output highlighted chunk. '''Output highlighted chunk.
This implementation just outputs :py:meth:`hlstyle` joined with This implementation just outputs :py:meth:`hlstyle` joined with
``contents``. ``contents``.
''' '''
return self.hlstyle(fg, bg, attrs) + (contents or '') return self.hlstyle(fg, bg, attrs, **kwargs) + (contents or '')

View File

@ -17,7 +17,7 @@ class I3barRenderer(Renderer):
# We dont need to explicitly reset attributes, so skip those calls # We dont need to explicitly reset attributes, so skip those calls
return '' return ''
def hl(self, contents, fg=None, bg=None, attrs=None): def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
segment = { segment = {
'full_text': contents, 'full_text': contents,
'separator': False, 'separator': False,

View File

@ -90,7 +90,7 @@ class IPythonPygmentsRenderer(IPythonRenderer):
def hl_join(segments): def hl_join(segments):
return reduce(operator.iadd, segments, []) return reduce(operator.iadd, segments, [])
def hl(self, contents, fg=None, bg=None, attrs=None): def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
'''Output highlighted chunk. '''Output highlighted chunk.
This implementation outputs a list containing a single pair This implementation outputs a list containing a single pair

View File

@ -21,7 +21,7 @@ class LemonbarRenderer(Renderer):
# We dont need to explicitly reset attributes, so skip those calls # We dont need to explicitly reset attributes, so skip those calls
return '' return ''
def hl(self, contents, fg=None, bg=None, attrs=None): def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
text = '' text = ''
if fg is not None: if fg is not None:

View File

@ -15,7 +15,7 @@ class PangoMarkupRenderer(Renderer):
# We dont need to explicitly reset attributes, so skip those calls # We dont need to explicitly reset attributes, so skip those calls
return '' return ''
def hl(self, contents, fg=None, bg=None, attrs=None): def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
'''Highlight a segment.''' '''Highlight a segment.'''
awesome_attr = [] awesome_attr = []
if fg is not None: if fg is not None:

View File

@ -105,7 +105,7 @@ class ShellRenderer(PromptRenderer):
self.used_term_escape_style = self.term_escape_style self.used_term_escape_style = self.term_escape_style
return super(ShellRenderer, self).do_render(segment_info=segment_info, **kwargs) return super(ShellRenderer, self).do_render(segment_info=segment_info, **kwargs)
def hlstyle(self, fg=None, bg=None, attrs=None): def hlstyle(self, fg=None, bg=None, attrs=None, escape=True, **kwargs):
'''Highlight a segment. '''Highlight a segment.
If an argument is None, the argument is ignored. If an argument is If an argument is None, the argument is ignored. If an argument is
@ -162,7 +162,7 @@ class ShellRenderer(PromptRenderer):
r = '\033Ptmux;' + r.replace('\033', '\033\033') + '\033\\' r = '\033Ptmux;' + r.replace('\033', '\033\033') + '\033\\'
elif self.screen_escape: elif self.screen_escape:
r = '\033P' + r.replace('\033', '\033\033') + '\033\\' r = '\033P' + r.replace('\033', '\033\033') + '\033\\'
return self.escape_hl_start + r + self.escape_hl_end return self.escape_hl_start + r + self.escape_hl_end if escape else r
def get_theme(self, matcher_info): def get_theme(self, matcher_info):
if not matcher_info: if not matcher_info:

View File

@ -6,13 +6,91 @@ from powerline.renderers.shell import ShellRenderer
class BashPromptRenderer(ShellRenderer): class BashPromptRenderer(ShellRenderer):
'''Powerline bash prompt segment renderer.''' '''Powerline bash prompt segment renderer.'''
escape_hl_start = '\[' escape_hl_start = '\\['
escape_hl_end = '\]' escape_hl_end = '\\]'
character_translations = ShellRenderer.character_translations.copy() character_translations = ShellRenderer.character_translations.copy()
character_translations[ord('$')] = '\\$' character_translations[ord('$')] = '\\$'
character_translations[ord('`')] = '\\`' character_translations[ord('`')] = '\\`'
character_translations[ord('\\')] = '\\\\' character_translations[ord('\\')] = '\\\\'
def do_render(self, side, line, width, output_width, output_raw, hl_args, **kwargs):
# we are rendering the normal left prompt
if side == 'left' and line == 0 and width is not None:
# we need left prompt's width to render the raw spacer
output_width = output_width or output_raw
left = super(BashPromptRenderer, self).do_render(
side=side,
line=line,
output_width=output_width,
width=width,
output_raw=output_raw,
hl_args=hl_args,
**kwargs
)
left_rendered = left[0] if output_width else left
# we don't escape color sequences in the right prompt so we can do escaping as a whole
if hl_args:
hl_args = hl_args.copy()
hl_args.update({'escape': False})
else:
hl_args = {'escape': False}
right = super(BashPromptRenderer, self).do_render(
side='right',
line=line,
output_width=True,
width=width,
output_raw=output_raw,
hl_args=hl_args,
**kwargs
)
ret = []
if right[-1] > 0:
# if the right prompt is not empty we embed it in the left prompt
# it must be escaped as a whole so readline doesn't see it
ret.append(''.join((
left_rendered,
self.escape_hl_start,
'\033[s', # save the cursor position
'\033[{0}C'.format(width), # move to the right edge of the terminal
'\033[{0}D'.format(right[-1] - 1), # move back to the right prompt position
right[0],
'\033[u', # restore the cursor position
self.escape_hl_end
)))
if output_raw:
ret.append(''.join((
left[1],
' ' * (width - left[-1] - right[-1]),
right[1]
)))
else:
ret.append(left_rendered)
if output_raw:
ret.append(left[1])
if output_width:
ret.append(left[-1])
if len(ret) == 1:
return ret[0]
else:
return ret
else:
return super(BashPromptRenderer, self).do_render(
side=side,
line=line,
width=width,
output_width=output_width,
output_raw=output_raw,
hl_args=hl_args,
**kwargs
)
renderer = BashPromptRenderer renderer = BashPromptRenderer

View File

@ -38,7 +38,7 @@ class TmuxRenderer(Renderer):
width = 10 width = 10
return super(TmuxRenderer, self).render(width=width, segment_info=segment_info, **kwargs) return super(TmuxRenderer, self).render(width=width, segment_info=segment_info, **kwargs)
def hlstyle(self, fg=None, bg=None, attrs=None): def hlstyle(self, fg=None, bg=None, attrs=None, **kwargs):
'''Highlight a segment.''' '''Highlight a segment.'''
# We dont need to explicitly reset attributes, so skip those calls # We dont need to explicitly reset attributes, so skip those calls
if not attrs and not bg and not fg: if not attrs and not bg and not fg:

View File

@ -123,7 +123,7 @@ class VimRenderer(Renderer):
def reset_highlight(self): def reset_highlight(self):
self.hl_groups.clear() self.hl_groups.clear()
def hlstyle(self, fg=None, bg=None, attrs=None): def hlstyle(self, fg=None, bg=None, attrs=None, **kwargs):
'''Highlight a segment. '''Highlight a segment.
If an argument is None, the argument is ignored. If an argument is If an argument is None, the argument is ignored. If an argument is

View File

@ -4,6 +4,7 @@ set_theme_option() {
set_theme() { set_theme() {
export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1" export POWERLINE_CONFIG_OVERRIDES="ext.shell.theme=$1"
} }
set_theme_option default.segment_data.hostname.args.only_if_ssh false
set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false set_theme_option default_leftonly.segment_data.hostname.args.only_if_ssh false
ABOVE_LEFT='[{ ABOVE_LEFT='[{
"left": [ "left": [
@ -40,7 +41,9 @@ VIRTUAL_ENV=
bgscript.sh & waitpid.sh bgscript.sh & waitpid.sh
false false
kill `cat pid` ; sleep 1s kill `cat pid` ; sleep 1s
set_theme_option default.segment_data.hostname.display false
set_theme_option default_leftonly.segment_data.hostname.display false set_theme_option default_leftonly.segment_data.hostname.display false
set_theme_option default.segment_data.user.display false
set_theme_option default_leftonly.segment_data.user.display false set_theme_option default_leftonly.segment_data.user.display false
echo ' echo '
abc abc
@ -56,14 +59,15 @@ cd ../'$(echo)'
cd ../'`echo`' cd ../'`echo`'
cd ../'«Unicode!»' cd ../'«Unicode!»'
(exit 42)|(exit 43) (exit 42)|(exit 43)
set_theme_option default_leftonly.segments.above "$ABOVE_LEFT" set_theme default
set_theme_option default.segments.above "$ABOVE_LEFT"
export DISPLAYED_ENV_VAR=foo export DISPLAYED_ENV_VAR=foo
unset DISPLAYED_ENV_VAR unset DISPLAYED_ENV_VAR
set_theme_option default_leftonly.segments.above "$ABOVE_FULL" set_theme_option default.segments.above "$ABOVE_FULL"
export DISPLAYED_ENV_VAR=foo export DISPLAYED_ENV_VAR=foo
unset DISPLAYED_ENV_VAR unset DISPLAYED_ENV_VAR
set_theme_option default_leftonly.segments.above set_theme_option default.segments.above
set_theme_option default_leftonly.dividers.left.hard \$ABC set_theme_option default.dividers.left.hard \$ABC
false false
true is the last line true is the last line
exit exit

View File

@ -7,7 +7,9 @@
  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
[1]+ Terminated bgscript.sh [1]+ Terminated bgscript.sh
  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false
  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
 USER   BRANCH  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false
 USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
  BRANCH  …  tmp  shell  3rd  echo '   BRANCH  …  tmp  shell  3rd  echo '
                                   abc                                    abc
@ -27,16 +29,17 @@ def
  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
  BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43)   BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43)
  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme_option default_leftonly.segments.above "$ABOVE_LEFT"   BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme default
  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo    shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above "$ABOVE_LEFT"
   shell  3rd  «Unicode!»     BRANCH export DISPLAYED_ENV_VAR=foo
 foo    foo  
  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR    shell  3rd  «Unicode!»     BRANCH unset DISPLAYED_ENV_VAR
  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above "$ABOVE_FULL"    shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above "$ABOVE_FULL"
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo    shell  3rd  «Unicode!»     BRANCH export DISPLAYED_ENV_VAR=foo
                                                                                                                                                                                                                                                                                                       foo                                                                                                                                                                                                                                                                                                         foo 
  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR    shell  3rd  «Unicode!»     BRANCH unset DISPLAYED_ENV_VAR
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above    shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above
  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC    shell  3rd  «Unicode!»     BRANCH set_theme_option default.dividers.left.hard \$ABC
  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse  …  shell  3rd  «Unicode!» $ABC   BRANCH false

View File

@ -7,7 +7,9 @@
  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  false
  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  1  1  kill `cat pid` ; sleep 1s
[1]+ Terminated bgscript.sh [1]+ Terminated bgscript.sh
  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default.segment_data.hostname.display false
  HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false   HOSTNAME  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.hostname.display false
 USER   BRANCH  …  tmp  shell  3rd  set_theme_option default.segment_data.user.display false
 USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false  USER   BRANCH  …  tmp  shell  3rd  set_theme_option default_leftonly.segment_data.user.display false
  BRANCH  …  tmp  shell  3rd  echo '   BRANCH  …  tmp  shell  3rd  echo '
   abc    abc
@ -27,16 +29,17 @@ def
  BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'   BRANCH  …  shell  3rd  $(echo)  cd ../'`echo`'
  BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'   BRANCH  …  shell  3rd  `echo`  cd ../'«Unicode!»'
  BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43)   BRANCH  …  shell  3rd  «Unicode!»  (exit 42)|(exit 43)
  BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme_option default_leftonly.segments.above "$ABOVE_LEFT"   BRANCH  …  shell  3rd  «Unicode!»  42  43  set_theme default
  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo    shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above "$ABOVE_LEFT"
   shell  3rd  «Unicode!»     BRANCH export DISPLAYED_ENV_VAR=foo
 foo    foo  
  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR    shell  3rd  «Unicode!»     BRANCH unset DISPLAYED_ENV_VAR
  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above "$ABOVE_FULL"    shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above "$ABOVE_FULL"
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
  BRANCH  …  shell  3rd  «Unicode!»  export DISPLAYED_ENV_VAR=foo    shell  3rd  «Unicode!»     BRANCH export DISPLAYED_ENV_VAR=foo
                                                                                                                                                                                                                                                                                                       foo                                                                                                                                                                                                                                                                                                         foo 
  BRANCH  …  shell  3rd  «Unicode!»  unset DISPLAYED_ENV_VAR    shell  3rd  «Unicode!»     BRANCH unset DISPLAYED_ENV_VAR
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.segments.above    shell  3rd  «Unicode!»     BRANCH set_theme_option default.segments.above
  BRANCH  …  shell  3rd  «Unicode!»  set_theme_option default_leftonly.dividers.left.hard \$ABC    shell  3rd  «Unicode!»     BRANCH set_theme_option default.dividers.left.hard \$ABC
  BRANCH $ABC…  shell  3rd  «Unicode!» $ABCfalse  …  shell  3rd  «Unicode!» $ABC   BRANCH false