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):
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.
When a width is provided, low-priority segments are dropped one at
@ -286,6 +286,11 @@ class Renderer(object):
:param matcher_info:
Matcher information. Is processed in :py:meth:`get_segment_info`
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)
return self.do_render(
@ -297,6 +302,7 @@ class Renderer(object):
output_width=output_width,
segment_info=self.get_segment_info(segment_info, mode),
theme=theme,
hl_args=hl_args
)
def compute_divider_widths(self, theme):
@ -324,7 +330,7 @@ class Renderer(object):
: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
'''
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)
hl_args = hl_args or dict()
if not width:
# No width specified, so we dont need to crop or pad anything
if output_width:
current_width = self._render_length(theme, segments, self.compute_divider_widths(theme))
return construct_returned_value(self.hl_join([
segment['_rendered_hl']
for segment in self._render_segments(theme, segments)
]) + self.hlstyle(), segments, current_width, output_raw, output_width)
for segment in self._render_segments(theme, segments, hl_args)
]) + self.hlstyle(**hl_args), segments, current_width, output_raw, output_width)
divider_widths = self.compute_divider_widths(theme)
@ -394,10 +402,10 @@ class Renderer(object):
rendered_highlighted = self.hl_join([
segment['_rendered_hl']
for segment in self._render_segments(theme, segments)
for segment in self._render_segments(theme, segments, hl_args)
])
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)
@ -470,7 +478,7 @@ class Renderer(object):
ret += segment_len
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.
This method loops through the segment array and compares the
@ -527,6 +535,10 @@ class Renderer(object):
contents_highlighted = ''
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
# segments are displayed. This is needed for Vim renderer to work.
if draw_divider:
@ -546,14 +558,14 @@ class Renderer(object):
if side == 'left':
if render_highlighted:
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False)
contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args)
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args)
segment['_rendered_raw'] = contents_raw + divider_raw
segment['_rendered_hl'] = contents_highlighted + divider_highlighted
else:
if render_highlighted:
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False)
contents_highlighted = self.hl(self.escape(contents_raw), **segment['highlight'])
divider_highlighted = self.hl(divider_raw, divider_fg, divider_bg, False, **hl_args)
contents_highlighted = self.hl(self.escape(contents_raw), **segment_hl_args)
segment['_rendered_raw'] = divider_raw + contents_raw
segment['_rendered_hl'] = divider_highlighted + contents_highlighted
else:
@ -562,7 +574,7 @@ class Renderer(object):
else:
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_hl'] = contents_highlighted
prev_segment = segment
@ -576,7 +588,7 @@ class Renderer(object):
'''
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.
Assuming highlighted string looks like ``{style}{contents}`` this method
@ -585,10 +597,10 @@ class Renderer(object):
'''
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.
This implementation just outputs :py:meth:`hlstyle` joined with
``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
return ''
def hl(self, contents, fg=None, bg=None, attrs=None):
def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
segment = {
'full_text': contents,
'separator': False,

View File

@ -90,7 +90,7 @@ class IPythonPygmentsRenderer(IPythonRenderer):
def hl_join(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.
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
return ''
def hl(self, contents, fg=None, bg=None, attrs=None):
def hl(self, contents, fg=None, bg=None, attrs=None, **kwargs):
text = ''
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
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.'''
awesome_attr = []
if fg is not None:

View File

@ -105,7 +105,7 @@ class ShellRenderer(PromptRenderer):
self.used_term_escape_style = self.term_escape_style
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.
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\\'
elif self.screen_escape:
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):
if not matcher_info:

View File

@ -6,13 +6,91 @@ from powerline.renderers.shell import ShellRenderer
class BashPromptRenderer(ShellRenderer):
'''Powerline bash prompt segment renderer.'''
escape_hl_start = '\['
escape_hl_end = '\]'
escape_hl_start = '\\['
escape_hl_end = '\\]'
character_translations = ShellRenderer.character_translations.copy()
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

View File

@ -38,7 +38,7 @@ class TmuxRenderer(Renderer):
width = 10
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.'''
# We dont need to explicitly reset attributes, so skip those calls
if not attrs and not bg and not fg:

View File

@ -123,7 +123,7 @@ class VimRenderer(Renderer):
def reset_highlight(self):
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.
If an argument is None, the argument is ignored. If an argument is

View File

@ -4,6 +4,7 @@ set_theme_option() {
set_theme() {
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
ABOVE_LEFT='[{
"left": [
@ -40,7 +41,9 @@ VIRTUAL_ENV=
bgscript.sh & waitpid.sh
false
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.segment_data.user.display false
set_theme_option default_leftonly.segment_data.user.display false
echo '
abc
@ -56,14 +59,15 @@ cd ../'$(echo)'
cd ../'`echo`'
cd ../'«Unicode!»'
(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
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
unset DISPLAYED_ENV_VAR
set_theme_option default_leftonly.segments.above
set_theme_option default_leftonly.dividers.left.hard \$ABC
set_theme_option default.segments.above
set_theme_option default.dividers.left.hard \$ABC
false
true is the last line
exit

View File

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