aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/ipython/py3/IPython/terminal/ptutils.py
diff options
context:
space:
mode:
authornkozlovskiy <nmk@ydb.tech>2023-09-29 12:24:06 +0300
committernkozlovskiy <nmk@ydb.tech>2023-09-29 12:41:34 +0300
commite0e3e1717e3d33762ce61950504f9637a6e669ed (patch)
treebca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/ipython/py3/IPython/terminal/ptutils.py
parent38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff)
downloadydb-e0e3e1717e3d33762ce61950504f9637a6e669ed.tar.gz
add ydb deps
Diffstat (limited to 'contrib/python/ipython/py3/IPython/terminal/ptutils.py')
-rw-r--r--contrib/python/ipython/py3/IPython/terminal/ptutils.py204
1 files changed, 204 insertions, 0 deletions
diff --git a/contrib/python/ipython/py3/IPython/terminal/ptutils.py b/contrib/python/ipython/py3/IPython/terminal/ptutils.py
new file mode 100644
index 0000000000..39bc2e15af
--- /dev/null
+++ b/contrib/python/ipython/py3/IPython/terminal/ptutils.py
@@ -0,0 +1,204 @@
+"""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.core.completer import (
+ provisionalcompleter, cursor_to_position,
+ _deduplicate_completions)
+from prompt_toolkit.completion import Completer, Completion
+from prompt_toolkit.lexers import Lexer
+from prompt_toolkit.lexers import PygmentsLexer
+from prompt_toolkit.patch_stdout import patch_stdout
+
+import pygments.lexers as pygments_lexers
+import os
+import sys
+import traceback
+
+_completion_sentinel = object()
+
+def _elide_point(string:str, *, min_elide=30)->str:
+ """
+ If a string is long enough, and has at least 3 dots,
+ replace the middle part with ellipses.
+
+ If a string naming a file is long enough, and has at least 3 slashes,
+ replace the middle part with ellipses.
+
+ If three consecutive dots, or two consecutive dots are encountered these are
+ replaced by the equivalents HORIZONTAL ELLIPSIS or TWO DOT LEADER unicode
+ equivalents
+ """
+ string = string.replace('...','\N{HORIZONTAL ELLIPSIS}')
+ string = string.replace('..','\N{TWO DOT LEADER}')
+ if len(string) < min_elide:
+ return string
+
+ object_parts = string.split('.')
+ file_parts = string.split(os.sep)
+ if file_parts[-1] == '':
+ file_parts.pop()
+
+ if len(object_parts) > 3:
+ return "{}.{}\N{HORIZONTAL ELLIPSIS}{}.{}".format(
+ object_parts[0],
+ object_parts[1][:1],
+ object_parts[-2][-1:],
+ object_parts[-1],
+ )
+
+ elif len(file_parts) > 3:
+ return ("{}" + os.sep + "{}\N{HORIZONTAL ELLIPSIS}{}" + os.sep + "{}").format(
+ file_parts[0], file_parts[1][:1], file_parts[-2][-1:], file_parts[-1]
+ )
+
+ return string
+
+def _elide_typed(string:str, typed:str, *, min_elide:int=30)->str:
+ """
+ Elide the middle of a long string if the beginning has already been typed.
+ """
+
+ if len(string) < min_elide:
+ return string
+ cut_how_much = len(typed)-3
+ if cut_how_much < 7:
+ return string
+ if string.startswith(typed) and len(string)> len(typed):
+ return f"{string[:3]}\N{HORIZONTAL ELLIPSIS}{string[cut_how_much:]}"
+ return string
+
+def _elide(string:str, typed:str, min_elide=30)->str:
+ return _elide_typed(
+ _elide_point(string, min_elide=min_elide),
+ typed, min_elide=min_elide)
+
+
+
+def _adjust_completion_text_based_on_context(text, body, offset):
+ if text.endswith('=') and len(body) > offset and body[offset] == '=':
+ return text[:-1]
+ else:
+ return text
+
+
+class IPythonPTCompleter(Completer):
+ """Adaptor to provide IPython completions to prompt_toolkit"""
+ def __init__(self, ipy_completer=None, shell=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
+
+ @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 patch_stdout(), provisionalcompleter():
+ 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)
+ try:
+ yield from self._get_completions(body, offset, cursor_position, self.ipy_completer)
+ except Exception as e:
+ try:
+ exc_type, exc_value, exc_tb = sys.exc_info()
+ traceback.print_exception(exc_type, exc_value, exc_tb)
+ except AttributeError:
+ print('Unrecoverable Error in completions')
+
+ @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 = _deduplicate_completions(
+ body, ipyc.completions(body, offset))
+ for c in completions:
+ if not c.text:
+ # Guard against completion machinery giving us an empty string.
+ continue
+ text = unicodedata.normalize('NFC', c.text)
+ # 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(text[0]) == 0:
+ if cursor_position + c.start > 0:
+ char_before = body[c.start - 1]
+ fixed_text = unicodedata.normalize(
+ 'NFC', char_before + text)
+
+ # Yield the modified completion instead, if this worked.
+ if wcwidth(text[0:1]) == 1:
+ yield Completion(fixed_text, start_position=c.start - offset - 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)
+ display_text = c.text
+
+ adjusted_text = _adjust_completion_text_based_on_context(c.text, body, offset)
+ if c.type == 'function':
+ yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text+'()', body[c.start:c.end]), display_meta=c.type+c.signature)
+ else:
+ yield Completion(adjusted_text, start_position=c.start - offset, display=_elide(display_text, body[c.start:c.end]), display_meta=c.type)
+
+class IPythonPTLexer(Lexer):
+ """
+ Wrapper around PythonLexer and BashLexer.
+ """
+ def __init__(self):
+ l = pygments_lexers
+ self.python_lexer = PygmentsLexer(l.Python3Lexer)
+ 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, 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(document)