diff --git a/docs/source/configuration/reference.rst b/docs/source/configuration/reference.rst index 4e3ec2b7..e19ef31a 100644 --- a/docs/source/configuration/reference.rst +++ b/docs/source/configuration/reference.rst @@ -26,6 +26,29 @@ Common configuration is a subdictionary that is a value of ``common`` key in to the terminal emulator. See the :ref:`term-feature-support-matrix` for information on whether your terminal emulator supports 24-bit colors. + This variable is forced to be ``false`` if :ref:`term_escape_style + ` option is set to ``"fbterm"`` or if it is + set to ``"auto"`` and powerline detected fbterm. + +.. _config-common-term_escape_style: + +``term_escape_style`` + Defines what escapes sequences should be used. Accepts three variants: + + ======= =================================================================== + Variant Description + ======= =================================================================== + auto ``xterm`` or ``fbterm`` depending on ``$TERM`` variable value: + ``TERM=fbterm`` implies ``fbterm`` escaping style, all other values + select ``xterm`` escaping. + xterm Uses ``\e[{fb};5;{color}m`` for colors (``{fb}`` is either ``38`` + (foreground) or ``48`` (background)). Should be used for most + terminals. + fbterm Uses ``\e[{fb};{color}}`` for colors (``{fb}`` is either ``1`` + (foreground) or ``2`` (background)). Should be used for fbterm: + framebuffer terminal. + ======= =================================================================== + .. _config-common-ambiwidth: ``ambiwidth`` diff --git a/docs/source/usage.rst b/docs/source/usage.rst index a5301c92..a8cf3bdc 100644 --- a/docs/source/usage.rst +++ b/docs/source/usage.rst @@ -52,6 +52,7 @@ if your terminal emulator supports it. Terminal.app OS X |i_yes| |i_no| |i_no| libvte-based [#]_ Linux |i_yes| |i_yes| |i_yes| [#]_ xterm Linux |i_yes| |i_no| |i_partial| [#]_ + fbterm Linux |i_yes| |i_yes| |i_no| ===================== ======= ===================== ===================== ===================== .. |i_yes| image:: _static/img/icons/tick.png diff --git a/powerline/__init__.py b/powerline/__init__.py index 026231b9..d0c820ce 100644 --- a/powerline/__init__.py +++ b/powerline/__init__.py @@ -258,6 +258,7 @@ def finish_common_config(encoding, common_config): common_config.setdefault('log_level', 'WARNING') common_config.setdefault('log_format', '%(asctime)s:%(levelname)s:%(message)s') common_config.setdefault('term_truecolor', False) + common_config.setdefault('term_escape_style', 'auto') common_config.setdefault('ambiwidth', 1) common_config.setdefault('additional_escapes', None) common_config.setdefault('reload_config', True) @@ -474,6 +475,7 @@ class Powerline(object): mergedicts(self.renderer_options, dict( pl=self.pl, term_truecolor=self.common_config['term_truecolor'], + term_escape_style=self.common_config['term_escape_style'], ambiwidth=self.common_config['ambiwidth'], tmux_escape=self.common_config['additional_escapes'] == 'tmux', screen_escape=self.common_config['additional_escapes'] == 'screen', diff --git a/powerline/lint/__init__.py b/powerline/lint/__init__.py index d1797d99..404500c0 100644 --- a/powerline/lint/__init__.py +++ b/powerline/lint/__init__.py @@ -57,6 +57,7 @@ main_spec = (Spec( common=Spec( default_top_theme=top_theme_spec().optional(), term_truecolor=Spec().type(bool).optional(), + term_escape_style=Spec().type(unicode).oneof(set(('auto', 'xterm', 'fbterm'))).optional(), # Python is capable of loading from zip archives. Thus checking path # only for existence of the path, not for it being a directory paths=Spec().list( diff --git a/powerline/renderer.py b/powerline/renderer.py index e69abca1..6c60fc79 100644 --- a/powerline/renderer.py +++ b/powerline/renderer.py @@ -291,7 +291,7 @@ class Renderer(object): line=line, output_raw=output_raw, output_width=output_width, - segment_info=segment_info, + segment_info=self.get_segment_info(segment_info, mode), theme=theme, ) @@ -310,7 +310,7 @@ class Renderer(object): def do_render(self, mode, width, side, line, output_raw, output_width, segment_info, theme): '''Like Renderer.render(), but accept theme in place of matcher_info ''' - segments = list(theme.get_segments(side, line, self.get_segment_info(segment_info, mode), mode)) + segments = list(theme.get_segments(side, line, segment_info, mode)) current_width = 0 diff --git a/powerline/renderers/shell/__init__.py b/powerline/renderers/shell/__init__.py index 3318ff22..90aa83ad 100644 --- a/powerline/renderers/shell/__init__.py +++ b/powerline/renderers/shell/__init__.py @@ -18,6 +18,7 @@ class ShellRenderer(Renderer): escape_hl_start = '' escape_hl_end = '' term_truecolor = False + term_escape_style = 'auto' tmux_escape = False screen_escape = False @@ -36,6 +37,13 @@ class ShellRenderer(Renderer): ) def do_render(self, output_width, segment_info, side, theme, width=None, **kwargs): + if self.term_escape_style == 'auto': + if segment_info['environ'].get('TERM') == 'fbterm': + self.used_term_escape_style = 'fbterm' + else: + self.used_term_escape_style = 'xterm' + else: + self.used_term_escape_style = self.term_escape_style if isinstance(segment_info, dict): client_id = segment_info.get('client_id') else: @@ -85,11 +93,13 @@ class ShellRenderer(Renderer): is a valid color or attribute, it’s added to the ANSI escape code. ''' ansi = [0] + is_fbterm = self.used_term_escape_style == 'fbterm' + term_truecolor = not is_fbterm and self.term_truecolor if fg is not None: if fg is False or fg[0] is False: ansi += [39] else: - if self.term_truecolor: + if term_truecolor: ansi += [38, 2] + list(int_to_rgb(fg[1])) else: ansi += [38, 5, fg[0]] @@ -97,7 +107,7 @@ class ShellRenderer(Renderer): if bg is False or bg[0] is False: ansi += [49] else: - if self.term_truecolor: + if term_truecolor: ansi += [48, 2] + list(int_to_rgb(bg[1])) else: ansi += [48, 5, bg[0]] @@ -113,7 +123,21 @@ class ShellRenderer(Renderer): ansi += [3] elif attr & ATTR_UNDERLINE: ansi += [4] - r = '\033[{0}m'.format(';'.join(str(attr) for attr in ansi)) + if is_fbterm: + r = [] + while ansi: + cur_ansi = ansi.pop(0) + if cur_ansi == 38: + ansi.pop(0) + r.append('\033[1;{0}}}'.format(ansi.pop(0))) + elif cur_ansi == 48: + ansi.pop(0) + r.append('\033[2;{0}}}'.format(ansi.pop(0))) + else: + r.append('\033[{0}m'.format(cur_ansi)) + r = ''.join(r) + else: + r = '\033[{0}m'.format(';'.join(str(attr) for attr in ansi)) if self.tmux_escape: r = '\033Ptmux;' + r.replace('\033', '\033\033') + '\033\\' elif self.screen_escape: diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 8ab6064f..dd776628 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -39,6 +39,10 @@ config = { 'theme': 'default', 'colorscheme': 'default', }, + 'shell': { + 'theme': 'default', + 'colorscheme': 'default', + }, }, }, 'colors': { @@ -131,6 +135,14 @@ config = { ], }, }, + 'themes/shell/default': { + 'default_module': 'powerline.segments.common', + 'segments': { + 'left': [ + highlighted_string('s', 'g1', width='auto'), + ], + }, + }, } @@ -639,6 +651,102 @@ class TestSegmentData(TestRender): self.assertRenderEqual(p, '{56} 1S{56}>{56}3S{610}>>{910}3S{910}>{910}2S{10-}>>{--}') +class TestShellEscapes(TestCase): + @with_new_config + def test_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0;38;5;5;48;5;6m\xa0s\x1b[0;38;5;6;49;22m>>\x1b[0m') + + @with_new_config + def test_tmux_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['additional_escapes'] = 'tmux' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0;38;5;5;48;5;6m\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0;38;5;6;49;22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_screen_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['additional_escapes'] = 'screen' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0;38;5;5;48;5;6m\x1b\\\xa0s\x1bP\x1b\x1b[0;38;5;6;49;22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_fbterm_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_escape_style'] = 'fbterm' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0m\x1b[1;5}\x1b[2;6}\xa0s\x1b[0m\x1b[1;6}\x1b[49m\x1b[22m>>\x1b[0m') + + @with_new_config + def test_fbterm_tmux_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_escape_style'] = 'fbterm' + config['config']['common']['additional_escapes'] = 'tmux' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0m\x1b\x1b[1;5}\x1b\x1b[2;6}\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0m\x1b\x1b[1;6}\x1b\x1b[49m\x1b\x1b[22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_fbterm_screen_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_escape_style'] = 'fbterm' + config['config']['common']['additional_escapes'] = 'screen' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0m\x1b\x1b[1;5}\x1b\x1b[2;6}\x1b\\\xa0s\x1bP\x1b\x1b[0m\x1b\x1b[1;6}\x1b\x1b[49m\x1b\x1b[22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_term_truecolor_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_truecolor'] = True + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0;38;2;192;0;192;48;2;0;128;128m\xa0s\x1b[0;38;2;0;128;128;49;22m>>\x1b[0m') + + @with_new_config + def test_term_truecolor_fbterm_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_escape_style'] = 'fbterm' + config['config']['common']['term_truecolor'] = True + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1b[0m\x1b[1;5}\x1b[2;6}\xa0s\x1b[0m\x1b[1;6}\x1b[49m\x1b[22m>>\x1b[0m') + + @with_new_config + def test_term_truecolor_tmux_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_truecolor'] = True + config['config']['common']['additional_escapes'] = 'tmux' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bPtmux;\x1b\x1b[0;38;2;192;0;192;48;2;0;128;128m\x1b\\\xa0s\x1bPtmux;\x1b\x1b[0;38;2;0;128;128;49;22m\x1b\\>>\x1bPtmux;\x1b\x1b[0m\x1b\\') + + @with_new_config + def test_term_truecolor_screen_escapes(self, config): + from powerline.shell import ShellPowerline + import powerline as powerline_module + config['config']['common']['term_truecolor'] = True + config['config']['common']['additional_escapes'] = 'screen' + with swap_attributes(config, powerline_module): + with get_powerline_raw(config, ShellPowerline, args=Args(config_path=[''])) as powerline: + self.assertEqual(powerline.render(segment_info={}, side='left'), '\x1bP\x1b\x1b[0;38;2;192;0;192;48;2;0;128;128m\x1b\\\xa0s\x1bP\x1b\x1b[0;38;2;0;128;128;49;22m\x1b\\>>\x1bP\x1b\x1b[0m\x1b\\') + + class TestVim(TestCase): def test_environ_update(self): # Regression test: test that segment obtains environment from vim, not