ptutils.py
126 lines
| 4.7 KiB
| text/x-python
|
PythonLexer
Min RK
|
r22768 | """prompt-toolkit utilities | ||
Everything in this module is a private API, | ||||
not to be used outside IPython. | ||||
""" | ||||
# Copyright (c) IPython Development Team. | ||||
# Distributed under the terms of the Modified BSD License. | ||||
Thomas Kluyver
|
r22388 | import unicodedata | ||
from wcwidth import wcwidth | ||||
Matthias Bussonnier
|
r23284 | from IPython.core.completer import IPCompleter, provisionalcompleter, rectify_completions, cursor_to_position | ||
Thomas Kluyver
|
r22388 | from prompt_toolkit.completion import Completer, Completion | ||
from prompt_toolkit.layout.lexers import Lexer | ||||
from prompt_toolkit.layout.lexers import PygmentsLexer | ||||
Jonathan Slenders
|
r22512 | import pygments.lexers as pygments_lexers | ||
Matthias Bussonnier
|
r23284 | _completion_sentinel = object() | ||
Thomas Kluyver
|
r22388 | |||
class IPythonPTCompleter(Completer): | ||||
"""Adaptor to provide IPython completions to prompt_toolkit""" | ||||
Thomas Kluyver
|
r23274 | def __init__(self, ipy_completer=None, shell=None, patch_stdout=None): | ||
Min RK
|
r22768 | if shell is None and ipy_completer is None: | ||
raise TypeError("Please pass shell=an InteractiveShell instance.") | ||||
self._ipy_completer = ipy_completer | ||||
Min RK
|
r22765 | self.shell = shell | ||
Thomas Kluyver
|
r23274 | if patch_stdout is None: | ||
raise TypeError("Please pass patch_stdout") | ||||
self.patch_stdout = patch_stdout | ||||
Min RK
|
r22767 | |||
Min RK
|
r22765 | @property | ||
def ipy_completer(self): | ||||
Min RK
|
r22768 | if self._ipy_completer: | ||
return self._ipy_completer | ||||
else: | ||||
return self.shell.Completer | ||||
Thomas Kluyver
|
r22388 | |||
def get_completions(self, document, complete_event): | ||||
if not document.current_line.strip(): | ||||
return | ||||
Thomas Kluyver
|
r23171 | # Some bits of our completion system may print stuff (e.g. if a module | ||
# is imported). This context manager ensures that doesn't interfere with | ||||
# the prompt. | ||||
Thomas Kluyver
|
r22445 | |||
Matthias Bussonnier
|
r23291 | with self.patch_stdout(), provisionalcompleter(): | ||
Matthias Bussonnier
|
r23284 | body = document.text | ||
cursor_row = document.cursor_position_row | ||||
cursor_col = document.cursor_position_col | ||||
cursor_position = document.cursor_position | ||||
offset = cursor_to_position(body, cursor_row, cursor_col) | ||||
yield from self._get_completions(body, offset, cursor_position, self.ipy_completer) | ||||
Thomas Kluyver
|
r22388 | |||
Matthias Bussonnier
|
r23284 | @staticmethod | ||
def _get_completions(body, offset, cursor_position, ipyc): | ||||
""" | ||||
Private equivalent of get_completions() use only for unit_testing. | ||||
""" | ||||
debug = getattr(ipyc, 'debug', False) | ||||
completions = rectify_completions( | ||||
body, ipyc.completions(body, offset), _debug=debug) | ||||
for c in completions: | ||||
if not c.text: | ||||
# Guard against completion machinery giving us an empty string. | ||||
continue | ||||
text = unicodedata.normalize('NFC', c.text) | ||||
Thomas Kluyver
|
r22388 | # When the first character of the completion has a zero length, | ||
# then it's probably a decomposed unicode character. E.g. caused by | ||||
# the "\dot" completion. Try to compose again with the previous | ||||
# character. | ||||
Matthias Bussonnier
|
r23284 | if wcwidth(text[0]) == 0: | ||
if cursor_position + c.start > 0: | ||||
char_before = body[c.start - 1] | ||||
fixed_text = unicodedata.normalize( | ||||
'NFC', char_before + text) | ||||
Thomas Kluyver
|
r22388 | |||
# Yield the modified completion instead, if this worked. | ||||
Matthias Bussonnier
|
r23284 | if wcwidth(text[0:1]) == 1: | ||
yield Completion(fixed_text, start_position=c.start - offset - 1) | ||||
Thomas Kluyver
|
r22388 | continue | ||
# TODO: Use Jedi to determine meta_text | ||||
# (Jedi currently has a bug that results in incorrect information.) | ||||
# meta_text = '' | ||||
# yield Completion(m, start_position=start_pos, | ||||
# display_meta=meta_text) | ||||
Matthias Bussonnier
|
r23284 | yield Completion(c.text, start_position=c.start - offset, display_meta=c.type) | ||
Thomas Kluyver
|
r22388 | |||
class IPythonPTLexer(Lexer): | ||||
""" | ||||
Wrapper around PythonLexer and BashLexer. | ||||
""" | ||||
def __init__(self): | ||||
Jonathan Slenders
|
r22512 | l = pygments_lexers | ||
Srinivas Reddy Thatiparthy
|
r23108 | self.python_lexer = PygmentsLexer(l.Python3Lexer) | ||
Jonathan Slenders
|
r22512 | self.shell_lexer = PygmentsLexer(l.BashLexer) | ||
self.magic_lexers = { | ||||
'HTML': PygmentsLexer(l.HtmlLexer), | ||||
'html': PygmentsLexer(l.HtmlLexer), | ||||
'javascript': PygmentsLexer(l.JavascriptLexer), | ||||
'js': PygmentsLexer(l.JavascriptLexer), | ||||
'perl': PygmentsLexer(l.PerlLexer), | ||||
'ruby': PygmentsLexer(l.RubyLexer), | ||||
'latex': PygmentsLexer(l.TexLexer), | ||||
} | ||||
Thomas Kluyver
|
r22388 | |||
def lex_document(self, cli, document): | ||||
Jonathan Slenders
|
r22512 | text = document.text.lstrip() | ||
lexer = self.python_lexer | ||||
if text.startswith('!') or text.startswith('%%bash'): | ||||
lexer = self.shell_lexer | ||||
elif text.startswith('%%'): | ||||
for magic, l in self.magic_lexers.items(): | ||||
if text.startswith('%%' + magic): | ||||
lexer = l | ||||
break | ||||
return lexer.lex_document(cli, document) | ||||