123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115 |
- """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.
- import unicodedata
- from wcwidth import wcwidth
- from IPython.utils.py3compat import PY3
- from IPython.core.completer import IPCompleter
- from prompt_toolkit.completion import Completer, Completion
- from prompt_toolkit.layout.lexers import Lexer
- from prompt_toolkit.layout.lexers import PygmentsLexer
- import pygments.lexers as pygments_lexers
- class IPythonPTCompleter(Completer):
- """Adaptor to provide IPython completions to prompt_toolkit"""
- def __init__(self, ipy_completer=None, shell=None, patch_stdout=None):
- if shell is None and ipy_completer is None:
- raise TypeError("Please pass shell=an InteractiveShell instance.")
- self._ipy_completer = ipy_completer
- self.shell = shell
- if patch_stdout is None:
- raise TypeError("Please pass patch_stdout")
- self.patch_stdout = patch_stdout
- @property
- def ipy_completer(self):
- if self._ipy_completer:
- return self._ipy_completer
- else:
- return self.shell.Completer
- def get_completions(self, document, complete_event):
- if not document.current_line.strip():
- return
- # 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.
- with self.patch_stdout():
- 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:
- if not m:
- # Guard against completion machinery giving us an empty string.
- continue
- 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):
- l = pygments_lexers
- self.python_lexer = PygmentsLexer(l.Python3Lexer if PY3 else l.PythonLexer)
- 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),
- }
- def lex_document(self, cli, document):
- 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)
|