* 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
Dictionary that maps characters in range 0x000x1F (inclusive) to strings
Dictionary that maps characters in range 0x000x1F (inclusive) to strings
``'^@'``, ``'^A'`` and so on.
.. note: maps tab to ``^I`` and newline to ``^J``.
'''
np_invalid_character_translations = dict((
# Invalid unicode characters obtained using 'surrogateescape' error
# Invalid unicode characters obtained using 'surrogateescape' error
# handler.
(i2, '<{0:02x}>'.format(i2 - 0xDC00)) for i2 in range(0xDC80, 0xDD00)
))
'''Invalid unicode character translations
When using ``surrogateescape`` encoding error handling method characters in
range 0x800xFF (inclusive) are transformed into unpaired surrogate escape
unicode codepoints 0xDC800xDD00. This dictionary maps such characters to
``<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
characters, not even paired ones. Python-2 contains a bug that allows such
When using ``surrogateescape`` encoding error handling method characters in
range 0x800xFF (inclusive) are transformed into unpaired surrogate escape
unicode codepoints 0xDC800xDD00. This dictionary maps such characters to
``<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
characters, not even paired ones. Python-2 contains a bug that allows such
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]')
'''Regex that finds unpaired surrogate escape characters
Search is only limited to the ones obtained from ``surrogateescape`` error
handling method. This regex is only used for UCS-2 Python variants because
in this case characters above 0xFFFF are represented as surrogate escapes
characters and are thus subject to partial transformation if
Search is only limited to the ones obtained from ``surrogateescape`` error
handling method. This regex is only used for UCS-2 Python variants because
in this case characters above 0xFFFF are represented as surrogate escapes
characters and are thus subject to partial transformation if
``np_invalid_character_translations`` translation table is used.
'''
np_character_translations = np_control_character_translations.copy()
'''Dictionary that contains non-printable character translations
In UCS-4 versions of Python this is a union of
``np_invalid_character_translations`` and ``np_control_character_translations``
dictionaries. In UCS-2 for technical reasons ``np_invalid_character_re`` is used
instead and this dictionary only contains items from
In UCS-4 versions of Python this is a union of
``np_invalid_character_translations`` and ``np_control_character_translations``
dictionaries. In UCS-2 for technical reasons ``np_invalid_character_re`` is used
instead and this dictionary only contains items from
``np_control_character_translations``.
'''
@ -81,10 +81,10 @@ translate_np = (
)
'''Function that translates non-printable characters into printable strings
Is used to translate control characters and surrogate escape characters
obtained from ``surrogateescape`` encoding errors handling method into some
printable sequences. See documentation for
``np_invalid_character_translations`` and
Is used to translate control characters and surrogate escape characters
obtained from ``surrogateescape`` encoding errors handling method into some
printable sequences. See documentation for
``np_invalid_character_translations`` and
``np_control_character_translations`` for more details.
'''
@ -106,17 +106,17 @@ class Renderer(object):
:param dict theme_config:
Main theme configuration.
: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.
:param dict theme_kwargs:
Keyword arguments for ``Theme`` class constructor.
:param PowerlineLogger pl:
Object used for logging.
: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).
: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.
'''
@ -127,23 +127,23 @@ class Renderer(object):
}
'''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:
``environ``
Object containing environment variables. Must define at least the
following methods: ``.__getitem__(var)`` that raises ``KeyError`` in
case requested environment variable is not present, ``.get(var,
default=None)`` that works like ``dict.get`` and be able to be passed to
Object containing environment variables. Must define at least the
following methods: ``.__getitem__(var)`` that raises ``KeyError`` in
case requested environment variable is not present, ``.get(var,
default=None)`` that works like ``dict.get`` and be able to be passed to
``Popen``.
``getcwd``
Function that returns current working directory. Will be called without
any arguments, should return ``unicode`` or (in python-2) regular
Function that returns current working directory. Will be called without
any arguments, should return ``unicode`` or (in python-2) regular
string.
``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``.
'''
@ -185,8 +185,8 @@ class Renderer(object):
)
'''Function that returns string width.
Is used to calculate the place given string occupies when handling
``width`` argument to ``.render()`` method. Must take east asian width
Is used to calculate the place given string occupies when handling
``width`` argument to ``.render()`` method. Must take east asian width
into account.
:param unicode string:
@ -198,18 +198,18 @@ class Renderer(object):
def get_theme(self, matcher_info):
'''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.
:param matcher_info:
Parameter ``matcher_info`` that ``.render()`` method received.
Parameter ``matcher_info`` that ``.render()`` method received.
Unused.
'''
return self.theme
def shutdown(self):
'''Prepare for interpreter shutdown. The only job it is supposed to do
is calling ``.shutdown()`` method for all theme objects. Should be
'''Prepare for interpreter shutdown. The only job it is supposed to do
is calling ``.shutdown()`` method for all theme objects. Should be
overridden by subclasses in case they support local themes.
'''
self.theme.shutdown()
@ -217,12 +217,12 @@ class Renderer(object):
def get_segment_info(self, segment_info, mode):
'''Get segment information.
Must return a dictionary containing at least ``home``, ``environ`` and
``getcwd`` keys (see documentation for ``segment_info`` attribute). This
implementation merges ``segment_info`` dictionary passed to
``.render()`` method with ``.segment_info`` attribute, preferring keys
from the former. It also replaces ``getcwd`` key with function returning
``segment_info['environ']['PWD']`` in case ``PWD`` variable is
Must return a dictionary containing at least ``home``, ``environ`` and
``getcwd`` keys (see documentation for ``segment_info`` attribute). This
implementation merges ``segment_info`` dictionary passed to
``.render()`` method with ``.segment_info`` attribute, preferring keys
from the former. It also replaces ``getcwd`` key with function returning
``segment_info['environ']['PWD']`` in case ``PWD`` variable is
available.
:param dict segment_info:
@ -241,7 +241,7 @@ class Renderer(object):
def render_above_lines(self, **kwargs):
'''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.
: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):
'''Render all segments.
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
with a negative priority are left. If one or more segments with
``"width": "auto"`` are provided they will fill the remaining space
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
with a negative priority are left. If one or more segments with
``"width": "auto"`` are provided they will fill the remaining space
until the desired width is reached.
: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.
: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.
: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.
: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).
:param bool output_raw:
Changes the output: if this parameter is ``True`` then in place of
one string this method outputs a pair ``(colored_string,
Changes the output: if this parameter is ``True`` then in place of
one string this method outputs a pair ``(colored_string,
colorless_string)``.
:param bool output_width:
Changes the output: if this parameter is ``True`` then in place of
one string this method outputs a pair ``(colored_string,
string_width)``. Returns a three-tuple if ``output_raw`` is also
Changes the output: if this parameter is ``True`` then in place of
one string this method outputs a pair ``(colored_string,
string_width)``. Returns a three-tuple if ``output_raw`` is also
``True``: ``(colored_string, colorless_string, string_width)``.
:param dict segment_info:
Segment information. See also :py:meth:`get_segment_info` method.
:param matcher_info:
Matcher information. Is processed in :py:meth:`get_segment_info`
Matcher information. Is processed in :py:meth:`get_segment_info`
method.
'''
theme = self.get_theme(matcher_info)
@ -314,11 +314,11 @@ class Renderer(object):
hl_join = staticmethod(''.join)
'''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.
:param list segments:
Iterable containing rendered segments. By rendered segments
Iterable containing rendered segments. By rendered segments
:py:meth:`Renderer.hl` output is meant.
:return: Results of joining these segments.
@ -355,7 +355,7 @@ class Renderer(object):
segments_priority = iter(segments_priority)
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
diff = current_width - width
for segment in segments_priority:
@ -365,8 +365,8 @@ class Renderer(object):
break
current_width = self._render_length(theme, segments, divider_widths)
if current_width > width:
# When there are not too much use more precise, but much slower
# width computation. It also finishes computations in case
# When there are not too much use more precise, but much slower
# width computation. It also finishes computations in case
# previous variant did not free enough space.
for segment in segments_priority:
segments.remove(segment)
@ -386,7 +386,7 @@ class Renderer(object):
distribute_len + (1 if distribute_len_remainder > 0 else 0),
segment))
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.
current_width = width
elif output_width:
@ -527,7 +527,7 @@ class Renderer(object):
contents_highlighted = ''
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.
if draw_divider:
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):
'''Output highlight style string.
Assuming highlighted string looks like ``{style}{contents}`` this method
should output ``{style}``. If it is called without arguments this method
Assuming highlighted string looks like ``{style}{contents}`` this method
should output ``{style}``. If it is called without arguments this method
is supposed to reset style to its default.
'''
raise NotImplementedError
@ -588,7 +588,7 @@ class Renderer(object):
def hl(self, contents, fg=None, bg=None, attrs=None):
'''Output highlighted chunk.
This implementation just outputs :py:meth:`hlstyle` joined with
This implementation just outputs :py:meth:`hlstyle` joined with
``contents``.
'''
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',
url='https://github.com/powerline/powerline',
license='MIT',
# 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
# 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
# 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.
# * Consider the following input:
# % alias hex1=$'hexdump -e \'"" 1/1 "%02X\n"\''
@ -111,7 +111,7 @@ setup(
# > 0A
# (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.
scripts=[
'scripts/powerline-lint',

View File

@ -1,6 +1,9 @@
from powerline.bindings.ipython.since_7 import PowerlinePrompts
import os
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.Powerline.config_paths = [os.path.abspath('powerline/config_files')]
c.Powerline.theme_overrides = {