* ipython fix

* ipython fix

* ipython fix

* fix ipython startup

* better fix for nbsp

* ipython7: fix attributes

* Change ipythen tests to use new binding

* ipython test: use proper config
This commit is contained in:
Philip Wellnitz 2019-10-08 03:29:43 +09:00 committed by GitHub
parent bc33b1ce31
commit f9ce8201c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 248 additions and 73 deletions

View File

@ -0,0 +1,81 @@
# vim:fileencoding=utf-8:noet
from __future__ import (unicode_literals, division, absolute_import, print_function)
from weakref import ref
from IPython.terminal.prompts import Prompts
from pygments.token import Token # NOQA
from powerline.ipython import IPythonPowerline
from powerline.renderers.ipython.since_7 import PowerlinePromptStyle
from powerline.bindings.ipython.post_0_11 import PowerlineMagics, ShutdownHook
class ConfigurableIPythonPowerline(IPythonPowerline):
def init(self, ip):
config = ip.config.Powerline
self.config_overrides = config.get('config_overrides')
self.theme_overrides = config.get('theme_overrides', {})
self.config_paths = config.get('config_paths')
super(ConfigurableIPythonPowerline, self).init(
renderer_module='.since_7')
def do_setup(self, ip, prompts, shutdown_hook):
prompts.powerline = self
msfn_missing = ()
saved_msfn = getattr(ip, '_make_style_from_name', msfn_missing)
if hasattr(saved_msfn, 'powerline_original'):
saved_msfn = saved_msfn.powerline_original
def _make_style_from_name(ip, name):
prev_style = saved_msfn(name)
new_style = PowerlinePromptStyle(lambda: prev_style)
return new_style
_make_style_from_name.powerline_original = saved_msfn
if not isinstance(ip._style, PowerlinePromptStyle):
prev_style = ip._style
ip._style = PowerlinePromptStyle(lambda: prev_style)
if not isinstance(saved_msfn, type(self.init)):
_saved_msfn = saved_msfn
saved_msfn = lambda: _saved_msfn(ip)
if saved_msfn is not msfn_missing:
ip._make_style_from_name = _make_style_from_name
magics = PowerlineMagics(ip, self)
ip.register_magics(magics)
if shutdown_hook:
shutdown_hook.powerline = ref(self)
class PowerlinePrompts(Prompts):
'''Class that returns powerline prompts
'''
def __init__(self, shell):
shutdown_hook = ShutdownHook(shell)
powerline = ConfigurableIPythonPowerline(shell)
self.shell = shell
powerline.do_setup(shell, self, shutdown_hook)
self.last_output_count = None
self.last_output = {}
for prompt in ('in', 'continuation', 'rewrite', 'out'):
exec((
'def {0}_prompt_tokens(self, *args, **kwargs):\n'
' if self.last_output_count != self.shell.execution_count:\n'
' self.last_output.clear()\n'
' self.last_output_count = self.shell.execution_count\n'
' if "{0}" not in self.last_output:\n'
' self.last_output["{0}"] = self.powerline.render('
' side="left",'
' matcher_info="{1}",'
' segment_info=self.shell,'
' ) + [(Token.Generic.Prompt, " ")]\n'
' return self.last_output["{0}"]'
).format(prompt, 'in2' if prompt == 'continuation' else prompt))

View File

@ -21,25 +21,25 @@ np_control_character_translations = dict((
)) ))
'''Control character translations '''Control character translations
Dictionary that maps characters in range 0x000x1F (inclusive) to strings Dictionary that maps characters in range 0x000x1F (inclusive) to strings
``'^@'``, ``'^A'`` and so on. ``'^@'``, ``'^A'`` and so on.
.. note: maps tab to ``^I`` and newline to ``^J``. .. note: maps tab to ``^I`` and newline to ``^J``.
''' '''
np_invalid_character_translations = dict(( np_invalid_character_translations = dict((
# Invalid unicode characters obtained using 'surrogateescape' error # Invalid unicode characters obtained using 'surrogateescape' error
# handler. # handler.
(i2, '<{0:02x}>'.format(i2 - 0xDC00)) for i2 in range(0xDC80, 0xDD00) (i2, '<{0:02x}>'.format(i2 - 0xDC00)) for i2 in range(0xDC80, 0xDD00)
)) ))
'''Invalid unicode character translations '''Invalid unicode character translations
When using ``surrogateescape`` encoding error handling method characters in When using ``surrogateescape`` encoding error handling method characters in
range 0x800xFF (inclusive) are transformed into unpaired surrogate escape range 0x800xFF (inclusive) are transformed into unpaired surrogate escape
unicode codepoints 0xDC800xDD00. This dictionary maps such characters to unicode codepoints 0xDC800xDD00. This dictionary maps such characters to
``<80>``, ``<81>``, and so on: in Python-3 they cannot be printed or ``<80>``, ``<81>``, and so on: in Python-3 they cannot be printed or
converted to UTF-8 because UTF-8 standard does not allow surrogate escape converted to UTF-8 because UTF-8 standard does not allow surrogate escape
characters, not even paired ones. Python-2 contains a bug that allows such characters, not even paired ones. Python-2 contains a bug that allows such
action, but printing them in any case makes no sense. action, but printing them in any case makes no sense.
''' '''
@ -47,20 +47,20 @@ action, but printing them in any case makes no sense.
np_invalid_character_re = re.compile('(?<![\uD800-\uDBFF])[\uDC80-\uDD00]') np_invalid_character_re = re.compile('(?<![\uD800-\uDBFF])[\uDC80-\uDD00]')
'''Regex that finds unpaired surrogate escape characters '''Regex that finds unpaired surrogate escape characters
Search is only limited to the ones obtained from ``surrogateescape`` error Search is only limited to the ones obtained from ``surrogateescape`` error
handling method. This regex is only used for UCS-2 Python variants because handling method. This regex is only used for UCS-2 Python variants because
in this case characters above 0xFFFF are represented as surrogate escapes in this case characters above 0xFFFF are represented as surrogate escapes
characters and are thus subject to partial transformation if characters and are thus subject to partial transformation if
``np_invalid_character_translations`` translation table is used. ``np_invalid_character_translations`` translation table is used.
''' '''
np_character_translations = np_control_character_translations.copy() np_character_translations = np_control_character_translations.copy()
'''Dictionary that contains non-printable character translations '''Dictionary that contains non-printable character translations
In UCS-4 versions of Python this is a union of In UCS-4 versions of Python this is a union of
``np_invalid_character_translations`` and ``np_control_character_translations`` ``np_invalid_character_translations`` and ``np_control_character_translations``
dictionaries. In UCS-2 for technical reasons ``np_invalid_character_re`` is used dictionaries. In UCS-2 for technical reasons ``np_invalid_character_re`` is used
instead and this dictionary only contains items from instead and this dictionary only contains items from
``np_control_character_translations``. ``np_control_character_translations``.
''' '''
@ -81,10 +81,10 @@ translate_np = (
) )
'''Function that translates non-printable characters into printable strings '''Function that translates non-printable characters into printable strings
Is used to translate control characters and surrogate escape characters Is used to translate control characters and surrogate escape characters
obtained from ``surrogateescape`` encoding errors handling method into some obtained from ``surrogateescape`` encoding errors handling method into some
printable sequences. See documentation for printable sequences. See documentation for
``np_invalid_character_translations`` and ``np_invalid_character_translations`` and
``np_control_character_translations`` for more details. ``np_control_character_translations`` for more details.
''' '''
@ -106,17 +106,17 @@ class Renderer(object):
:param dict theme_config: :param dict theme_config:
Main theme configuration. Main theme configuration.
:param local_themes: :param local_themes:
Local themes. Is to be used by subclasses from ``.get_theme()`` method, Local themes. Is to be used by subclasses from ``.get_theme()`` method,
base class only records this parameter to a ``.local_themes`` attribute. base class only records this parameter to a ``.local_themes`` attribute.
:param dict theme_kwargs: :param dict theme_kwargs:
Keyword arguments for ``Theme`` class constructor. Keyword arguments for ``Theme`` class constructor.
:param PowerlineLogger pl: :param PowerlineLogger pl:
Object used for logging. Object used for logging.
:param int ambiwidth: :param int ambiwidth:
Width of the characters with east asian width unicode attribute equal to Width of the characters with east asian width unicode attribute equal to
``A`` (Ambigious). ``A`` (Ambigious).
:param dict options: :param dict options:
Various options. Are normally not used by base renderer, but all options Various options. Are normally not used by base renderer, but all options
are recorded as attributes. are recorded as attributes.
''' '''
@ -127,23 +127,23 @@ class Renderer(object):
} }
'''Basic segment info '''Basic segment info
Is merged with local segment information by :py:meth:`get_segment_info` Is merged with local segment information by :py:meth:`get_segment_info`
method. Keys: method. Keys:
``environ`` ``environ``
Object containing environment variables. Must define at least the Object containing environment variables. Must define at least the
following methods: ``.__getitem__(var)`` that raises ``KeyError`` in following methods: ``.__getitem__(var)`` that raises ``KeyError`` in
case requested environment variable is not present, ``.get(var, case requested environment variable is not present, ``.get(var,
default=None)`` that works like ``dict.get`` and be able to be passed to default=None)`` that works like ``dict.get`` and be able to be passed to
``Popen``. ``Popen``.
``getcwd`` ``getcwd``
Function that returns current working directory. Will be called without Function that returns current working directory. Will be called without
any arguments, should return ``unicode`` or (in python-2) regular any arguments, should return ``unicode`` or (in python-2) regular
string. string.
``home`` ``home``
String containing path to home directory. Should be ``unicode`` or (in String containing path to home directory. Should be ``unicode`` or (in
python-2) regular string or ``None``. python-2) regular string or ``None``.
''' '''
@ -185,8 +185,8 @@ class Renderer(object):
) )
'''Function that returns string width. '''Function that returns string width.
Is used to calculate the place given string occupies when handling Is used to calculate the place given string occupies when handling
``width`` argument to ``.render()`` method. Must take east asian width ``width`` argument to ``.render()`` method. Must take east asian width
into account. into account.
:param unicode string: :param unicode string:
@ -198,18 +198,18 @@ class Renderer(object):
def get_theme(self, matcher_info): def get_theme(self, matcher_info):
'''Get Theme object. '''Get Theme object.
Is to be overridden by subclasses to support local themes, this variant Is to be overridden by subclasses to support local themes, this variant
only returns ``.theme`` attribute. only returns ``.theme`` attribute.
:param matcher_info: :param matcher_info:
Parameter ``matcher_info`` that ``.render()`` method received. Parameter ``matcher_info`` that ``.render()`` method received.
Unused. Unused.
''' '''
return self.theme return self.theme
def shutdown(self): def shutdown(self):
'''Prepare for interpreter shutdown. The only job it is supposed to do '''Prepare for interpreter shutdown. The only job it is supposed to do
is calling ``.shutdown()`` method for all theme objects. Should be is calling ``.shutdown()`` method for all theme objects. Should be
overridden by subclasses in case they support local themes. overridden by subclasses in case they support local themes.
''' '''
self.theme.shutdown() self.theme.shutdown()
@ -217,12 +217,12 @@ class Renderer(object):
def get_segment_info(self, segment_info, mode): def get_segment_info(self, segment_info, mode):
'''Get segment information. '''Get segment information.
Must return a dictionary containing at least ``home``, ``environ`` and Must return a dictionary containing at least ``home``, ``environ`` and
``getcwd`` keys (see documentation for ``segment_info`` attribute). This ``getcwd`` keys (see documentation for ``segment_info`` attribute). This
implementation merges ``segment_info`` dictionary passed to implementation merges ``segment_info`` dictionary passed to
``.render()`` method with ``.segment_info`` attribute, preferring keys ``.render()`` method with ``.segment_info`` attribute, preferring keys
from the former. It also replaces ``getcwd`` key with function returning from the former. It also replaces ``getcwd`` key with function returning
``segment_info['environ']['PWD']`` in case ``PWD`` variable is ``segment_info['environ']['PWD']`` in case ``PWD`` variable is
available. available.
:param dict segment_info: :param dict segment_info:
@ -241,7 +241,7 @@ class Renderer(object):
def render_above_lines(self, **kwargs): def render_above_lines(self, **kwargs):
'''Render all segments in the {theme}/segments/above list '''Render all segments in the {theme}/segments/above list
Rendering happens in the reversed order. Parameters are the same as in Rendering happens in the reversed order. Parameters are the same as in
.render() method. .render() method.
:yield: rendered line. :yield: rendered line.
@ -254,37 +254,37 @@ class Renderer(object):
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):
'''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
a time until the line is shorter than the width, or only segments a time until the line is shorter than the width, or only segments
with a negative priority are left. If one or more segments with with a negative priority are left. If one or more segments with
``"width": "auto"`` are provided they will fill the remaining space ``"width": "auto"`` are provided they will fill the remaining space
until the desired width is reached. until the desired width is reached.
:param str mode: :param str mode:
Mode string. Affects contents (colors and the set of segments) of Mode string. Affects contents (colors and the set of segments) of
rendered string. rendered string.
:param int width: :param int width:
Maximum width text can occupy. May be exceeded if there are too much Maximum width text can occupy. May be exceeded if there are too much
non-removable segments. non-removable segments.
:param str side: :param str side:
One of ``left``, ``right``. Determines which side will be rendered. One of ``left``, ``right``. Determines which side will be rendered.
If not present all sides are rendered. If not present all sides are rendered.
:param int line: :param int line:
Line number for which segments should be obtained. Is counted from Line number for which segments should be obtained. Is counted from
zero (botmost line). zero (botmost line).
:param bool output_raw: :param bool output_raw:
Changes the output: if this parameter is ``True`` then in place of Changes the output: if this parameter is ``True`` then in place of
one string this method outputs a pair ``(colored_string, one string this method outputs a pair ``(colored_string,
colorless_string)``. colorless_string)``.
:param bool output_width: :param bool output_width:
Changes the output: if this parameter is ``True`` then in place of Changes the output: if this parameter is ``True`` then in place of
one string this method outputs a pair ``(colored_string, one string this method outputs a pair ``(colored_string,
string_width)``. Returns a three-tuple if ``output_raw`` is also string_width)``. Returns a three-tuple if ``output_raw`` is also
``True``: ``(colored_string, colorless_string, string_width)``. ``True``: ``(colored_string, colorless_string, string_width)``.
:param dict segment_info: :param dict segment_info:
Segment information. See also :py:meth:`get_segment_info` method. Segment information. See also :py:meth:`get_segment_info` method.
: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.
''' '''
theme = self.get_theme(matcher_info) theme = self.get_theme(matcher_info)
@ -314,11 +314,11 @@ class Renderer(object):
hl_join = staticmethod(''.join) hl_join = staticmethod(''.join)
'''Join a list of rendered segments into a resulting string '''Join a list of rendered segments into a resulting string
This method exists to deal with non-string render outputs, so `segments` This method exists to deal with non-string render outputs, so `segments`
may actually be not an iterable with strings. may actually be not an iterable with strings.
:param list segments: :param list segments:
Iterable containing rendered segments. By rendered segments Iterable containing rendered segments. By rendered segments
:py:meth:`Renderer.hl` output is meant. :py:meth:`Renderer.hl` output is meant.
:return: Results of joining these segments. :return: Results of joining these segments.
@ -355,7 +355,7 @@ class Renderer(object):
segments_priority = iter(segments_priority) segments_priority = iter(segments_priority)
if current_width > width and len(segments) > 100: if current_width > width and len(segments) > 100:
# When there are too many segments use faster, but less correct # When there are too many segments use faster, but less correct
# algorythm for width computation # algorythm for width computation
diff = current_width - width diff = current_width - width
for segment in segments_priority: for segment in segments_priority:
@ -365,8 +365,8 @@ class Renderer(object):
break break
current_width = self._render_length(theme, segments, divider_widths) current_width = self._render_length(theme, segments, divider_widths)
if current_width > width: if current_width > width:
# When there are not too much use more precise, but much slower # When there are not too much use more precise, but much slower
# width computation. It also finishes computations in case # width computation. It also finishes computations in case
# previous variant did not free enough space. # previous variant did not free enough space.
for segment in segments_priority: for segment in segments_priority:
segments.remove(segment) segments.remove(segment)
@ -386,7 +386,7 @@ class Renderer(object):
distribute_len + (1 if distribute_len_remainder > 0 else 0), distribute_len + (1 if distribute_len_remainder > 0 else 0),
segment)) segment))
distribute_len_remainder -= 1 distribute_len_remainder -= 1
# `_len` key is not needed anymore, but current_width should have an # `_len` key is not needed anymore, but current_width should have an
# actual value for various bindings. # actual value for various bindings.
current_width = width current_width = width
elif output_width: elif output_width:
@ -527,7 +527,7 @@ class Renderer(object):
contents_highlighted = '' contents_highlighted = ''
draw_divider = segment['draw_' + divider_type + '_divider'] draw_divider = segment['draw_' + divider_type + '_divider']
# 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:
divider_raw = self.escape(theme.get_divider(side, divider_type)) divider_raw = self.escape(theme.get_divider(side, divider_type))
@ -579,8 +579,8 @@ class Renderer(object):
def hlstyle(fg=None, bg=None, attrs=None): def hlstyle(fg=None, bg=None, attrs=None):
'''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
should output ``{style}``. If it is called without arguments this method should output ``{style}``. If it is called without arguments this method
is supposed to reset style to its default. is supposed to reset style to its default.
''' '''
raise NotImplementedError raise NotImplementedError
@ -588,7 +588,7 @@ class Renderer(object):
def hl(self, contents, fg=None, bg=None, attrs=None): def hl(self, contents, fg=None, bg=None, attrs=None):
'''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) + (contents or '')

View File

@ -0,0 +1,91 @@
# vim:fileencoding=utf-8:noet
import operator
try:
from __builtin__ import reduce
except ImportError:
from functools import reduce
from pygments.token import Token
from prompt_toolkit.styles import DynamicStyle
from powerline.renderers.ipython import IPythonRenderer
from powerline.ipython import IPythonInfo
from powerline.colorscheme import ATTR_BOLD, ATTR_ITALIC, ATTR_UNDERLINE
used_styles = []
seen = set()
class PowerlinePromptStyle(DynamicStyle):
@property
def style_rules(self):
return (self.get_style() or self._dummy).style_rules + used_styles
def invalidation_hash(self):
return (h + 1 for h in tuple(super(PowerlinePromptStyle, self).invalidation_hash()))
class IPythonPygmentsRenderer(IPythonRenderer):
reduce_initial = []
def __init__(self, **kwargs):
super(IPythonPygmentsRenderer, self).__init__(**kwargs)
self.character_translations[ord(' ')] = ' '
def get_segment_info(self, segment_info, mode):
return super(IPythonPygmentsRenderer, self).get_segment_info(
IPythonInfo(segment_info), mode)
@staticmethod
def hl_join(segments):
return reduce(operator.iadd, segments, [])
def hl(self, escaped_contents, fg=None, bg=None, attrs=None, *args, **kwargs):
'''Output highlighted chunk.
This implementation outputs a list containing a single pair
(:py:class:`string`,
:py:class:`powerline.lib.unicode.unicode`).
'''
guifg = None
guibg = None
att = []
if fg is not None and fg is not False:
guifg = fg[1]
if bg is not None and bg is not False:
guibg = bg[1]
if attrs:
att = []
if attrs & ATTR_BOLD:
att.append('bold')
if attrs & ATTR_ITALIC:
att.append('italic')
if attrs & ATTR_UNDERLINE:
att.append('underline')
fg = (('%06x' % guifg) if guifg is not None else '')
bg = (('%06x' % guibg) if guibg is not None else '')
name = (
'pl'
+ ''.join(('_a' + attr for attr in att))
+ '_f' + fg + '_b' + bg
)
global seen
if not (name in seen):
global used_styles
used_styles += [('pygments.' + name,
''.join((' ' + attr for attr in att))
+ (' fg:#' + fg if fg != '' else ' fg:')
+ (' bg:#' + bg if bg != '' else ' bg:'))]
seen.add(name)
return [((name,), escaped_contents)]
def hlstyle(self, *args, **kwargs):
return []
def get_client_id(self, segment_info):
return id(self)
renderer = IPythonPygmentsRenderer

View File

@ -95,11 +95,11 @@ setup(
author_email='kim.silkebaekken+vim@gmail.com', author_email='kim.silkebaekken+vim@gmail.com',
url='https://github.com/powerline/powerline', url='https://github.com/powerline/powerline',
license='MIT', license='MIT',
# XXX Python 3 doesnt allow compiled C files to be included in the scripts # XXX Python 3 doesnt allow compiled C files to be included in the scripts
# list below. This is because Python 3 distutils tries to decode the file to # list below. This is because Python 3 distutils tries to decode the file to
# ASCII, and fails when powerline-client is a binary. # ASCII, and fails when powerline-client is a binary.
# #
# XXX Python 2 fucks up script contents*. Not using it to install scripts # XXX Python 2 fucks up script contents*. Not using it to install scripts
# any longer. # any longer.
# * Consider the following input: # * Consider the following input:
# % alias hex1=$'hexdump -e \'"" 1/1 "%02X\n"\'' # % alias hex1=$'hexdump -e \'"" 1/1 "%02X\n"\''
@ -111,7 +111,7 @@ setup(
# > 0A # > 0A
# (repeated, with diff segment header numbers growing up). # (repeated, with diff segment header numbers growing up).
# #
# FIXME Current solution does not work with `pip install -e`. Still better # FIXME Current solution does not work with `pip install -e`. Still better
# then solution that is not working at all. # then solution that is not working at all.
scripts=[ scripts=[
'scripts/powerline-lint', 'scripts/powerline-lint',

View File

@ -1,6 +1,9 @@
from powerline.bindings.ipython.since_7 import PowerlinePrompts
import os import os
c = get_config() c = get_config()
c.InteractiveShellApp.extensions = ['powerline.bindings.ipython.post_0_11'] c.TerminalInteractiveShell.simple_prompt = False
c.TerminalIPythonApp.display_banner = False
c.TerminalInteractiveShell.prompts_class = PowerlinePrompts
c.TerminalInteractiveShell.autocall = 1 c.TerminalInteractiveShell.autocall = 1
c.Powerline.config_paths = [os.path.abspath('powerline/config_files')] c.Powerline.config_paths = [os.path.abspath('powerline/config_files')]
c.Powerline.theme_overrides = { c.Powerline.theme_overrides = {