import unicodedata from wcwidth import wcwidth from IPython.utils.py3compat import PY3 from prompt_toolkit.completion import Completer, Completion from prompt_toolkit.layout.lexers import Lexer from prompt_toolkit.layout.lexers import PygmentsLexer from pygments.lexers import Python3Lexer, BashLexer, PythonLexer class IPythonPTCompleter(Completer): """Adaptor to provide IPython completions to prompt_toolkit""" def __init__(self, ipy_completer): self.ipy_completer = ipy_completer def get_completions(self, document, complete_event): if not document.current_line.strip(): return used, matches = self.ipy_completer.complete( line_buffer=document.current_line, cursor_pos=document.cursor_position_col ) start_pos = -len(used) for m in matches: m = unicodedata.normalize('NFC', m) # 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. if wcwidth(m[0]) == 0: if document.cursor_position + start_pos > 0: char_before = document.text[document.cursor_position + start_pos - 1] m = unicodedata.normalize('NFC', char_before + m) # Yield the modified completion instead, if this worked. if wcwidth(m[0:1]) == 1: yield Completion(m, start_position=start_pos - 1) 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) yield Completion(m, start_position=start_pos) class IPythonPTLexer(Lexer): """ Wrapper around PythonLexer and BashLexer. """ def __init__(self): self.python_lexer = PygmentsLexer(Python3Lexer if PY3 else PythonLexer) self.shell_lexer = PygmentsLexer(BashLexer) def lex_document(self, cli, document): if document.text.startswith('!'): return self.shell_lexer.lex_document(cli, document) else: return self.python_lexer.lex_document(cli, document)