diff options
author | shadchin <shadchin@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:44:30 +0300 |
commit | 2598ef1d0aee359b4b6d5fdd1758916d5907d04f (patch) | |
tree | 012bb94d777798f1f56ac1cec429509766d05181 /contrib/python/ipython/py3/IPython/terminal | |
parent | 6751af0b0c1b952fede40b19b71da8025b5d8bcf (diff) | |
download | ydb-2598ef1d0aee359b4b6d5fdd1758916d5907d04f.tar.gz |
Restoring authorship annotation for <shadchin@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/ipython/py3/IPython/terminal')
21 files changed, 3372 insertions, 3372 deletions
diff --git a/contrib/python/ipython/py3/IPython/terminal/console.py b/contrib/python/ipython/py3/IPython/terminal/console.py index 65571a7572..ccf3478c3d 100644 --- a/contrib/python/ipython/py3/IPython/terminal/console.py +++ b/contrib/python/ipython/py3/IPython/terminal/console.py @@ -1,19 +1,19 @@ -""" -Shim to maintain backwards compatibility with old IPython.terminal.console imports. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import sys -from warnings import warn - -from IPython.utils.shimmodule import ShimModule, ShimWarning - -warn("The `IPython.terminal.console` package has been deprecated since IPython 4.0. " - "You should import from jupyter_console instead.", ShimWarning) - -# Unconditionally insert the shim into sys.modules so that further import calls -# trigger the custom attribute access above - -sys.modules['IPython.terminal.console'] = ShimModule( - src='IPython.terminal.console', mirror='jupyter_console') +""" +Shim to maintain backwards compatibility with old IPython.terminal.console imports. +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import sys +from warnings import warn + +from IPython.utils.shimmodule import ShimModule, ShimWarning + +warn("The `IPython.terminal.console` package has been deprecated since IPython 4.0. " + "You should import from jupyter_console instead.", ShimWarning) + +# Unconditionally insert the shim into sys.modules so that further import calls +# trigger the custom attribute access above + +sys.modules['IPython.terminal.console'] = ShimModule( + src='IPython.terminal.console', mirror='jupyter_console') diff --git a/contrib/python/ipython/py3/IPython/terminal/debugger.py b/contrib/python/ipython/py3/IPython/terminal/debugger.py index db8ecac0d2..f9a4ca445f 100644 --- a/contrib/python/ipython/py3/IPython/terminal/debugger.py +++ b/contrib/python/ipython/py3/IPython/terminal/debugger.py @@ -1,146 +1,146 @@ -import asyncio -import signal -import sys - -from IPython.core.debugger import Pdb -from IPython.core.completer import IPCompleter -from .ptutils import IPythonPTCompleter -from .shortcuts import create_ipython_shortcuts, suspend_to_bg, cursor_in_leading_ws - -from prompt_toolkit.enums import DEFAULT_BUFFER -from prompt_toolkit.filters import (Condition, has_focus, has_selection, - vi_insert_mode, emacs_insert_mode) -from prompt_toolkit.key_binding import KeyBindings -from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline -from pygments.token import Token -from prompt_toolkit.shortcuts.prompt import PromptSession -from prompt_toolkit.enums import EditingMode -from prompt_toolkit.formatted_text import PygmentsTokens -from concurrent.futures import ThreadPoolExecutor - -from prompt_toolkit import __version__ as ptk_version -PTK3 = ptk_version.startswith('3.') - - -class TerminalPdb(Pdb): - """Standalone IPython debugger.""" - - def __init__(self, *args, pt_session_options=None, **kwargs): - Pdb.__init__(self, *args, **kwargs) - self._ptcomp = None - self.pt_init(pt_session_options) - self.thread_executor = ThreadPoolExecutor(1) - - def pt_init(self, pt_session_options=None): - """Initialize the prompt session and the prompt loop - and store them in self.pt_app and self.pt_loop. - - Additional keyword arguments for the PromptSession class - can be specified in pt_session_options. - """ - if pt_session_options is None: - pt_session_options = {} - - def get_prompt_tokens(): - return [(Token.Prompt, self.prompt)] - - if self._ptcomp is None: - compl = IPCompleter(shell=self.shell, - namespace={}, - global_namespace={}, - parent=self.shell, - ) - # add a completer for all the do_ methods - methods_names = [m[3:] for m in dir(self) if m.startswith("do_")] - - def gen_comp(self, text): - return [m for m in methods_names if m.startswith(text)] - import types - newcomp = types.MethodType(gen_comp, compl) - compl.custom_matchers.insert(0, newcomp) - # end add completer. - - self._ptcomp = IPythonPTCompleter(compl) - - options = dict( - message=(lambda: PygmentsTokens(get_prompt_tokens())), - editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), - key_bindings=create_ipython_shortcuts(self.shell), - history=self.shell.debugger_history, - completer=self._ptcomp, - enable_history_search=True, - mouse_support=self.shell.mouse_support, - complete_style=self.shell.pt_complete_style, - style=getattr(self.shell, "style", None), - color_depth=self.shell.color_depth, - ) - - if not PTK3: - options['inputhook'] = self.shell.inputhook - options.update(pt_session_options) - self.pt_loop = asyncio.new_event_loop() - self.pt_app = PromptSession(**options) - - def cmdloop(self, intro=None): - """Repeatedly issue a prompt, accept input, parse an initial prefix - off the received input, and dispatch to action methods, passing them - the remainder of the line as argument. - - override the same methods from cmd.Cmd to provide prompt toolkit replacement. - """ - if not self.use_rawinput: - raise ValueError('Sorry ipdb does not support use_rawinput=False') - - # In order to make sure that prompt, which uses asyncio doesn't - # interfere with applications in which it's used, we always run the - # prompt itself in a different thread (we can't start an event loop - # within an event loop). This new thread won't have any event loop - # running, and here we run our prompt-loop. - self.preloop() - - try: - if intro is not None: - self.intro = intro - if self.intro: - print(self.intro, file=self.stdout) - stop = None - while not stop: - if self.cmdqueue: - line = self.cmdqueue.pop(0) - else: - self._ptcomp.ipy_completer.namespace = self.curframe_locals - self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals - - # Run the prompt in a different thread. - try: - line = self.thread_executor.submit(self.pt_app.prompt).result() - except EOFError: - line = "EOF" - - line = self.precmd(line) - stop = self.onecmd(line) - stop = self.postcmd(stop, line) - self.postloop() - except Exception: - raise - - -def set_trace(frame=None): - """ - Start debugging from `frame`. - - If frame is not specified, debugging starts from caller's frame. - """ - TerminalPdb().set_trace(frame or sys._getframe().f_back) - - -if __name__ == '__main__': - import pdb - # IPython.core.debugger.Pdb.trace_dispatch shall not catch - # bdb.BdbQuit. When started through __main__ and an exception - # happened after hitting "c", this is needed in order to - # be able to quit the debugging session (see #9950). - old_trace_dispatch = pdb.Pdb.trace_dispatch - pdb.Pdb = TerminalPdb - pdb.Pdb.trace_dispatch = old_trace_dispatch - pdb.main() +import asyncio +import signal +import sys + +from IPython.core.debugger import Pdb +from IPython.core.completer import IPCompleter +from .ptutils import IPythonPTCompleter +from .shortcuts import create_ipython_shortcuts, suspend_to_bg, cursor_in_leading_ws + +from prompt_toolkit.enums import DEFAULT_BUFFER +from prompt_toolkit.filters import (Condition, has_focus, has_selection, + vi_insert_mode, emacs_insert_mode) +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline +from pygments.token import Token +from prompt_toolkit.shortcuts.prompt import PromptSession +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.formatted_text import PygmentsTokens +from concurrent.futures import ThreadPoolExecutor + +from prompt_toolkit import __version__ as ptk_version +PTK3 = ptk_version.startswith('3.') + + +class TerminalPdb(Pdb): + """Standalone IPython debugger.""" + + def __init__(self, *args, pt_session_options=None, **kwargs): + Pdb.__init__(self, *args, **kwargs) + self._ptcomp = None + self.pt_init(pt_session_options) + self.thread_executor = ThreadPoolExecutor(1) + + def pt_init(self, pt_session_options=None): + """Initialize the prompt session and the prompt loop + and store them in self.pt_app and self.pt_loop. + + Additional keyword arguments for the PromptSession class + can be specified in pt_session_options. + """ + if pt_session_options is None: + pt_session_options = {} + + def get_prompt_tokens(): + return [(Token.Prompt, self.prompt)] + + if self._ptcomp is None: + compl = IPCompleter(shell=self.shell, + namespace={}, + global_namespace={}, + parent=self.shell, + ) + # add a completer for all the do_ methods + methods_names = [m[3:] for m in dir(self) if m.startswith("do_")] + + def gen_comp(self, text): + return [m for m in methods_names if m.startswith(text)] + import types + newcomp = types.MethodType(gen_comp, compl) + compl.custom_matchers.insert(0, newcomp) + # end add completer. + + self._ptcomp = IPythonPTCompleter(compl) + + options = dict( + message=(lambda: PygmentsTokens(get_prompt_tokens())), + editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()), + key_bindings=create_ipython_shortcuts(self.shell), + history=self.shell.debugger_history, + completer=self._ptcomp, + enable_history_search=True, + mouse_support=self.shell.mouse_support, + complete_style=self.shell.pt_complete_style, + style=getattr(self.shell, "style", None), + color_depth=self.shell.color_depth, + ) + + if not PTK3: + options['inputhook'] = self.shell.inputhook + options.update(pt_session_options) + self.pt_loop = asyncio.new_event_loop() + self.pt_app = PromptSession(**options) + + def cmdloop(self, intro=None): + """Repeatedly issue a prompt, accept input, parse an initial prefix + off the received input, and dispatch to action methods, passing them + the remainder of the line as argument. + + override the same methods from cmd.Cmd to provide prompt toolkit replacement. + """ + if not self.use_rawinput: + raise ValueError('Sorry ipdb does not support use_rawinput=False') + + # In order to make sure that prompt, which uses asyncio doesn't + # interfere with applications in which it's used, we always run the + # prompt itself in a different thread (we can't start an event loop + # within an event loop). This new thread won't have any event loop + # running, and here we run our prompt-loop. + self.preloop() + + try: + if intro is not None: + self.intro = intro + if self.intro: + print(self.intro, file=self.stdout) + stop = None + while not stop: + if self.cmdqueue: + line = self.cmdqueue.pop(0) + else: + self._ptcomp.ipy_completer.namespace = self.curframe_locals + self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals + + # Run the prompt in a different thread. + try: + line = self.thread_executor.submit(self.pt_app.prompt).result() + except EOFError: + line = "EOF" + + line = self.precmd(line) + stop = self.onecmd(line) + stop = self.postcmd(stop, line) + self.postloop() + except Exception: + raise + + +def set_trace(frame=None): + """ + Start debugging from `frame`. + + If frame is not specified, debugging starts from caller's frame. + """ + TerminalPdb().set_trace(frame or sys._getframe().f_back) + + +if __name__ == '__main__': + import pdb + # IPython.core.debugger.Pdb.trace_dispatch shall not catch + # bdb.BdbQuit. When started through __main__ and an exception + # happened after hitting "c", this is needed in order to + # be able to quit the debugging session (see #9950). + old_trace_dispatch = pdb.Pdb.trace_dispatch + pdb.Pdb = TerminalPdb + pdb.Pdb.trace_dispatch = old_trace_dispatch + pdb.main() diff --git a/contrib/python/ipython/py3/IPython/terminal/embed.py b/contrib/python/ipython/py3/IPython/terminal/embed.py index 188844fadd..25ddd53afa 100644 --- a/contrib/python/ipython/py3/IPython/terminal/embed.py +++ b/contrib/python/ipython/py3/IPython/terminal/embed.py @@ -1,399 +1,399 @@ -# encoding: utf-8 -""" -An embedded IPython shell. -""" -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - -import sys -import warnings - -from IPython.core import ultratb, compilerop -from IPython.core import magic_arguments -from IPython.core.magic import Magics, magics_class, line_magic -from IPython.core.interactiveshell import DummyMod, InteractiveShell -from IPython.terminal.interactiveshell import TerminalInteractiveShell -from IPython.terminal.ipapp import load_default_config - -from traitlets import Bool, CBool, Unicode -from IPython.utils.io import ask_yes_no - -class KillEmbedded(Exception):pass - -# kept for backward compatibility as IPython 6 was released with -# the typo. See https://github.com/ipython/ipython/pull/10706 -KillEmbeded = KillEmbedded - -# This is an additional magic that is exposed in embedded shells. -@magics_class -class EmbeddedMagics(Magics): - - @line_magic - @magic_arguments.magic_arguments() - @magic_arguments.argument('-i', '--instance', action='store_true', - help='Kill instance instead of call location') - @magic_arguments.argument('-x', '--exit', action='store_true', - help='Also exit the current session') - @magic_arguments.argument('-y', '--yes', action='store_true', - help='Do not ask confirmation') - def kill_embedded(self, parameter_s=''): - """%kill_embedded : deactivate for good the current embedded IPython - - This function (after asking for confirmation) sets an internal flag so - that an embedded IPython will never activate again for the given call - location. This is useful to permanently disable a shell that is being - called inside a loop: once you've figured out what you needed from it, - you may then kill it and the program will then continue to run without - the interactive shell interfering again. - - - Kill Instance Option: - - If for some reasons you need to kill the location where the instance - is created and not called, for example if you create a single - instance in one place and debug in many locations, you can use the - ``--instance`` option to kill this specific instance. Like for the - ``call location`` killing an "instance" should work even if it is - recreated within a loop. - - .. note:: - - This was the default behavior before IPython 5.2 - - """ - - args = magic_arguments.parse_argstring(self.kill_embedded, parameter_s) - print(args) - if args.instance: - # let no ask - if not args.yes: - kill = ask_yes_no( - "Are you sure you want to kill this embedded instance? [y/N] ", 'n') - else: - kill = True - if kill: - self.shell._disable_init_location() - print("This embedded IPython instance will not reactivate anymore " - "once you exit.") - else: - if not args.yes: - kill = ask_yes_no( - "Are you sure you want to kill this embedded call_location? [y/N] ", 'n') - else: - kill = True - if kill: - self.shell.embedded_active = False - print("This embedded IPython call location will not reactivate anymore " - "once you exit.") - - if args.exit: - # Ask-exit does not really ask, it just set internals flags to exit - # on next loop. - self.shell.ask_exit() - - - @line_magic - def exit_raise(self, parameter_s=''): - """%exit_raise Make the current embedded kernel exit and raise and exception. - - This function sets an internal flag so that an embedded IPython will - raise a `IPython.terminal.embed.KillEmbedded` Exception on exit, and then exit the current I. This is - useful to permanently exit a loop that create IPython embed instance. - """ - - self.shell.should_raise = True - self.shell.ask_exit() - - - -class InteractiveShellEmbed(TerminalInteractiveShell): - - dummy_mode = Bool(False) - exit_msg = Unicode('') - embedded = CBool(True) - should_raise = CBool(False) - # Like the base class display_banner is not configurable, but here it - # is True by default. - display_banner = CBool(True) - exit_msg = Unicode() - - # When embedding, by default we don't change the terminal title - term_title = Bool(False, - help="Automatically set the terminal title" - ).tag(config=True) - - _inactive_locations = set() - - @property - def embedded_active(self): - return (self._call_location_id not in InteractiveShellEmbed._inactive_locations)\ - and (self._init_location_id not in InteractiveShellEmbed._inactive_locations) - - def _disable_init_location(self): - """Disable the current Instance creation location""" - InteractiveShellEmbed._inactive_locations.add(self._init_location_id) - - @embedded_active.setter - def embedded_active(self, value): - if value: - InteractiveShellEmbed._inactive_locations.discard( - self._call_location_id) - InteractiveShellEmbed._inactive_locations.discard( - self._init_location_id) - else: - InteractiveShellEmbed._inactive_locations.add( - self._call_location_id) - - def __init__(self, **kw): - if kw.get('user_global_ns', None) is not None: - raise DeprecationWarning( - "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0.") - - clid = kw.pop('_init_location_id', None) - if not clid: - frame = sys._getframe(1) - clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) - self._init_location_id = clid - - super(InteractiveShellEmbed,self).__init__(**kw) - - # don't use the ipython crash handler so that user exceptions aren't - # trapped - sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors, - mode=self.xmode, - call_pdb=self.pdb) - - def init_sys_modules(self): - """ - Explicitly overwrite :mod:`IPython.core.interactiveshell` to do nothing. - """ - pass - - def init_magics(self): - super(InteractiveShellEmbed, self).init_magics() - self.register_magics(EmbeddedMagics) - - def __call__(self, header='', local_ns=None, module=None, dummy=None, - stack_depth=1, global_ns=None, compile_flags=None, **kw): - """Activate the interactive interpreter. - - __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start - the interpreter shell with the given local and global namespaces, and - optionally print a header string at startup. - - The shell can be globally activated/deactivated using the - dummy_mode attribute. This allows you to turn off a shell used - for debugging globally. - - However, *each* time you call the shell you can override the current - state of dummy_mode with the optional keyword parameter 'dummy'. For - example, if you set dummy mode on with IPShell.dummy_mode = True, you - can still have a specific call work by making it as IPShell(dummy=False). - """ - - # we are called, set the underlying interactiveshell not to exit. - self.keep_running = True - - # If the user has turned it off, go away - clid = kw.pop('_call_location_id', None) - if not clid: - frame = sys._getframe(1) - clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) - self._call_location_id = clid - - if not self.embedded_active: - return - - # Normal exits from interactive mode set this flag, so the shell can't - # re-enter (it checks this variable at the start of interactive mode). - self.exit_now = False - - # Allow the dummy parameter to override the global __dummy_mode - if dummy or (dummy != 0 and self.dummy_mode): - return - - # self.banner is auto computed - if header: - self.old_banner2 = self.banner2 - self.banner2 = self.banner2 + '\n' + header + '\n' - else: - self.old_banner2 = '' - - if self.display_banner: - self.show_banner() - - # Call the embedding code with a stack depth of 1 so it can skip over - # our call and get the original caller's namespaces. - self.mainloop(local_ns, module, stack_depth=stack_depth, - global_ns=global_ns, compile_flags=compile_flags) - - self.banner2 = self.old_banner2 - - if self.exit_msg is not None: - print(self.exit_msg) - - if self.should_raise: - raise KillEmbedded('Embedded IPython raising error, as user requested.') - - - def mainloop(self, local_ns=None, module=None, stack_depth=0, - display_banner=None, global_ns=None, compile_flags=None): - """Embeds IPython into a running python program. - - Parameters - ---------- - - local_ns, module - Working local namespace (a dict) and module (a module or similar - object). If given as None, they are automatically taken from the scope - where the shell was called, so that program variables become visible. - - stack_depth : int - How many levels in the stack to go to looking for namespaces (when - local_ns or module is None). This allows an intermediate caller to - make sure that this function gets the namespace from the intended - level in the stack. By default (0) it will get its locals and globals - from the immediate caller. - - compile_flags - A bit field identifying the __future__ features - that are enabled, as passed to the builtin :func:`compile` function. - If given as None, they are automatically taken from the scope where - the shell was called. - - """ - - if (global_ns is not None) and (module is None): - raise DeprecationWarning("'global_ns' keyword argument is deprecated, and has been removed in IPython 5.0 use `module` keyword argument instead.") - - if (display_banner is not None): - warnings.warn("The display_banner parameter is deprecated since IPython 4.0", DeprecationWarning) - - # Get locals and globals from caller - if ((local_ns is None or module is None or compile_flags is None) - and self.default_user_namespaces): - call_frame = sys._getframe(stack_depth).f_back - - if local_ns is None: - local_ns = call_frame.f_locals - if module is None: - global_ns = call_frame.f_globals - try: - module = sys.modules[global_ns['__name__']] - except KeyError: - warnings.warn("Failed to get module %s" % \ - global_ns.get('__name__', 'unknown module') - ) - module = DummyMod() - module.__dict__ = global_ns - if compile_flags is None: - compile_flags = (call_frame.f_code.co_flags & - compilerop.PyCF_MASK) - - # Save original namespace and module so we can restore them after - # embedding; otherwise the shell doesn't shut down correctly. - orig_user_module = self.user_module - orig_user_ns = self.user_ns - orig_compile_flags = self.compile.flags - - # Update namespaces and fire up interpreter - - # The global one is easy, we can just throw it in - if module is not None: - self.user_module = module - - # But the user/local one is tricky: ipython needs it to store internal - # data, but we also need the locals. We'll throw our hidden variables - # like _ih and get_ipython() into the local namespace, but delete them - # later. - if local_ns is not None: - reentrant_local_ns = {k: v for (k, v) in local_ns.items() if k not in self.user_ns_hidden.keys()} - self.user_ns = reentrant_local_ns - self.init_user_ns() - - # Compiler flags - if compile_flags is not None: - self.compile.flags = compile_flags - - # make sure the tab-completer has the correct frame information, so it - # actually completes using the frame's locals/globals - self.set_completer_frame() - - with self.builtin_trap, self.display_trap: - self.interact() - - # now, purge out the local namespace of IPython's hidden variables. - if local_ns is not None: - local_ns.update({k: v for (k, v) in self.user_ns.items() if k not in self.user_ns_hidden.keys()}) - - - # Restore original namespace so shell can shut down when we exit. - self.user_module = orig_user_module - self.user_ns = orig_user_ns - self.compile.flags = orig_compile_flags - - -def embed(**kwargs): - """Call this to embed IPython at the current point in your program. - - The first invocation of this will create an :class:`InteractiveShellEmbed` - instance and then call it. Consecutive calls just call the already - created instance. - - If you don't want the kernel to initialize the namespace - from the scope of the surrounding function, - and/or you want to load full IPython configuration, - you probably want `IPython.start_ipython()` instead. - - Here is a simple example:: - - from IPython import embed - a = 10 - b = 20 - embed(header='First time') - c = 30 - d = 40 - embed() - - Full customization can be done by passing a :class:`Config` in as the - config argument. - """ - config = kwargs.get('config') - header = kwargs.pop('header', u'') - compile_flags = kwargs.pop('compile_flags', None) - if config is None: - config = load_default_config() - config.InteractiveShellEmbed = config.TerminalInteractiveShell - kwargs['config'] = config - using = kwargs.get('using', 'sync') - if using : - kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor', 'autoawait': using!='sync'}}) - #save ps1/ps2 if defined - ps1 = None - ps2 = None - try: - ps1 = sys.ps1 - ps2 = sys.ps2 - except AttributeError: - pass - #save previous instance - saved_shell_instance = InteractiveShell._instance - if saved_shell_instance is not None: - cls = type(saved_shell_instance) - cls.clear_instance() - frame = sys._getframe(1) - shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( - frame.f_code.co_filename, frame.f_lineno), **kwargs) - shell(header=header, stack_depth=2, compile_flags=compile_flags, - _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) - InteractiveShellEmbed.clear_instance() - #restore previous instance - if saved_shell_instance is not None: - cls = type(saved_shell_instance) - cls.clear_instance() - for subclass in cls._walk_mro(): - subclass._instance = saved_shell_instance - if ps1 is not None: - sys.ps1 = ps1 - sys.ps2 = ps2 +# encoding: utf-8 +""" +An embedded IPython shell. +""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +import sys +import warnings + +from IPython.core import ultratb, compilerop +from IPython.core import magic_arguments +from IPython.core.magic import Magics, magics_class, line_magic +from IPython.core.interactiveshell import DummyMod, InteractiveShell +from IPython.terminal.interactiveshell import TerminalInteractiveShell +from IPython.terminal.ipapp import load_default_config + +from traitlets import Bool, CBool, Unicode +from IPython.utils.io import ask_yes_no + +class KillEmbedded(Exception):pass + +# kept for backward compatibility as IPython 6 was released with +# the typo. See https://github.com/ipython/ipython/pull/10706 +KillEmbeded = KillEmbedded + +# This is an additional magic that is exposed in embedded shells. +@magics_class +class EmbeddedMagics(Magics): + + @line_magic + @magic_arguments.magic_arguments() + @magic_arguments.argument('-i', '--instance', action='store_true', + help='Kill instance instead of call location') + @magic_arguments.argument('-x', '--exit', action='store_true', + help='Also exit the current session') + @magic_arguments.argument('-y', '--yes', action='store_true', + help='Do not ask confirmation') + def kill_embedded(self, parameter_s=''): + """%kill_embedded : deactivate for good the current embedded IPython + + This function (after asking for confirmation) sets an internal flag so + that an embedded IPython will never activate again for the given call + location. This is useful to permanently disable a shell that is being + called inside a loop: once you've figured out what you needed from it, + you may then kill it and the program will then continue to run without + the interactive shell interfering again. + + + Kill Instance Option: + + If for some reasons you need to kill the location where the instance + is created and not called, for example if you create a single + instance in one place and debug in many locations, you can use the + ``--instance`` option to kill this specific instance. Like for the + ``call location`` killing an "instance" should work even if it is + recreated within a loop. + + .. note:: + + This was the default behavior before IPython 5.2 + + """ + + args = magic_arguments.parse_argstring(self.kill_embedded, parameter_s) + print(args) + if args.instance: + # let no ask + if not args.yes: + kill = ask_yes_no( + "Are you sure you want to kill this embedded instance? [y/N] ", 'n') + else: + kill = True + if kill: + self.shell._disable_init_location() + print("This embedded IPython instance will not reactivate anymore " + "once you exit.") + else: + if not args.yes: + kill = ask_yes_no( + "Are you sure you want to kill this embedded call_location? [y/N] ", 'n') + else: + kill = True + if kill: + self.shell.embedded_active = False + print("This embedded IPython call location will not reactivate anymore " + "once you exit.") + + if args.exit: + # Ask-exit does not really ask, it just set internals flags to exit + # on next loop. + self.shell.ask_exit() + + + @line_magic + def exit_raise(self, parameter_s=''): + """%exit_raise Make the current embedded kernel exit and raise and exception. + + This function sets an internal flag so that an embedded IPython will + raise a `IPython.terminal.embed.KillEmbedded` Exception on exit, and then exit the current I. This is + useful to permanently exit a loop that create IPython embed instance. + """ + + self.shell.should_raise = True + self.shell.ask_exit() + + + +class InteractiveShellEmbed(TerminalInteractiveShell): + + dummy_mode = Bool(False) + exit_msg = Unicode('') + embedded = CBool(True) + should_raise = CBool(False) + # Like the base class display_banner is not configurable, but here it + # is True by default. + display_banner = CBool(True) + exit_msg = Unicode() + + # When embedding, by default we don't change the terminal title + term_title = Bool(False, + help="Automatically set the terminal title" + ).tag(config=True) + + _inactive_locations = set() + + @property + def embedded_active(self): + return (self._call_location_id not in InteractiveShellEmbed._inactive_locations)\ + and (self._init_location_id not in InteractiveShellEmbed._inactive_locations) + + def _disable_init_location(self): + """Disable the current Instance creation location""" + InteractiveShellEmbed._inactive_locations.add(self._init_location_id) + + @embedded_active.setter + def embedded_active(self, value): + if value: + InteractiveShellEmbed._inactive_locations.discard( + self._call_location_id) + InteractiveShellEmbed._inactive_locations.discard( + self._init_location_id) + else: + InteractiveShellEmbed._inactive_locations.add( + self._call_location_id) + + def __init__(self, **kw): + if kw.get('user_global_ns', None) is not None: + raise DeprecationWarning( + "Key word argument `user_global_ns` has been replaced by `user_module` since IPython 4.0.") + + clid = kw.pop('_init_location_id', None) + if not clid: + frame = sys._getframe(1) + clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) + self._init_location_id = clid + + super(InteractiveShellEmbed,self).__init__(**kw) + + # don't use the ipython crash handler so that user exceptions aren't + # trapped + sys.excepthook = ultratb.FormattedTB(color_scheme=self.colors, + mode=self.xmode, + call_pdb=self.pdb) + + def init_sys_modules(self): + """ + Explicitly overwrite :mod:`IPython.core.interactiveshell` to do nothing. + """ + pass + + def init_magics(self): + super(InteractiveShellEmbed, self).init_magics() + self.register_magics(EmbeddedMagics) + + def __call__(self, header='', local_ns=None, module=None, dummy=None, + stack_depth=1, global_ns=None, compile_flags=None, **kw): + """Activate the interactive interpreter. + + __call__(self,header='',local_ns=None,module=None,dummy=None) -> Start + the interpreter shell with the given local and global namespaces, and + optionally print a header string at startup. + + The shell can be globally activated/deactivated using the + dummy_mode attribute. This allows you to turn off a shell used + for debugging globally. + + However, *each* time you call the shell you can override the current + state of dummy_mode with the optional keyword parameter 'dummy'. For + example, if you set dummy mode on with IPShell.dummy_mode = True, you + can still have a specific call work by making it as IPShell(dummy=False). + """ + + # we are called, set the underlying interactiveshell not to exit. + self.keep_running = True + + # If the user has turned it off, go away + clid = kw.pop('_call_location_id', None) + if not clid: + frame = sys._getframe(1) + clid = '%s:%s' % (frame.f_code.co_filename, frame.f_lineno) + self._call_location_id = clid + + if not self.embedded_active: + return + + # Normal exits from interactive mode set this flag, so the shell can't + # re-enter (it checks this variable at the start of interactive mode). + self.exit_now = False + + # Allow the dummy parameter to override the global __dummy_mode + if dummy or (dummy != 0 and self.dummy_mode): + return + + # self.banner is auto computed + if header: + self.old_banner2 = self.banner2 + self.banner2 = self.banner2 + '\n' + header + '\n' + else: + self.old_banner2 = '' + + if self.display_banner: + self.show_banner() + + # Call the embedding code with a stack depth of 1 so it can skip over + # our call and get the original caller's namespaces. + self.mainloop(local_ns, module, stack_depth=stack_depth, + global_ns=global_ns, compile_flags=compile_flags) + + self.banner2 = self.old_banner2 + + if self.exit_msg is not None: + print(self.exit_msg) + + if self.should_raise: + raise KillEmbedded('Embedded IPython raising error, as user requested.') + + + def mainloop(self, local_ns=None, module=None, stack_depth=0, + display_banner=None, global_ns=None, compile_flags=None): + """Embeds IPython into a running python program. + + Parameters + ---------- + + local_ns, module + Working local namespace (a dict) and module (a module or similar + object). If given as None, they are automatically taken from the scope + where the shell was called, so that program variables become visible. + + stack_depth : int + How many levels in the stack to go to looking for namespaces (when + local_ns or module is None). This allows an intermediate caller to + make sure that this function gets the namespace from the intended + level in the stack. By default (0) it will get its locals and globals + from the immediate caller. + + compile_flags + A bit field identifying the __future__ features + that are enabled, as passed to the builtin :func:`compile` function. + If given as None, they are automatically taken from the scope where + the shell was called. + + """ + + if (global_ns is not None) and (module is None): + raise DeprecationWarning("'global_ns' keyword argument is deprecated, and has been removed in IPython 5.0 use `module` keyword argument instead.") + + if (display_banner is not None): + warnings.warn("The display_banner parameter is deprecated since IPython 4.0", DeprecationWarning) + + # Get locals and globals from caller + if ((local_ns is None or module is None or compile_flags is None) + and self.default_user_namespaces): + call_frame = sys._getframe(stack_depth).f_back + + if local_ns is None: + local_ns = call_frame.f_locals + if module is None: + global_ns = call_frame.f_globals + try: + module = sys.modules[global_ns['__name__']] + except KeyError: + warnings.warn("Failed to get module %s" % \ + global_ns.get('__name__', 'unknown module') + ) + module = DummyMod() + module.__dict__ = global_ns + if compile_flags is None: + compile_flags = (call_frame.f_code.co_flags & + compilerop.PyCF_MASK) + + # Save original namespace and module so we can restore them after + # embedding; otherwise the shell doesn't shut down correctly. + orig_user_module = self.user_module + orig_user_ns = self.user_ns + orig_compile_flags = self.compile.flags + + # Update namespaces and fire up interpreter + + # The global one is easy, we can just throw it in + if module is not None: + self.user_module = module + + # But the user/local one is tricky: ipython needs it to store internal + # data, but we also need the locals. We'll throw our hidden variables + # like _ih and get_ipython() into the local namespace, but delete them + # later. + if local_ns is not None: + reentrant_local_ns = {k: v for (k, v) in local_ns.items() if k not in self.user_ns_hidden.keys()} + self.user_ns = reentrant_local_ns + self.init_user_ns() + + # Compiler flags + if compile_flags is not None: + self.compile.flags = compile_flags + + # make sure the tab-completer has the correct frame information, so it + # actually completes using the frame's locals/globals + self.set_completer_frame() + + with self.builtin_trap, self.display_trap: + self.interact() + + # now, purge out the local namespace of IPython's hidden variables. + if local_ns is not None: + local_ns.update({k: v for (k, v) in self.user_ns.items() if k not in self.user_ns_hidden.keys()}) + + + # Restore original namespace so shell can shut down when we exit. + self.user_module = orig_user_module + self.user_ns = orig_user_ns + self.compile.flags = orig_compile_flags + + +def embed(**kwargs): + """Call this to embed IPython at the current point in your program. + + The first invocation of this will create an :class:`InteractiveShellEmbed` + instance and then call it. Consecutive calls just call the already + created instance. + + If you don't want the kernel to initialize the namespace + from the scope of the surrounding function, + and/or you want to load full IPython configuration, + you probably want `IPython.start_ipython()` instead. + + Here is a simple example:: + + from IPython import embed + a = 10 + b = 20 + embed(header='First time') + c = 30 + d = 40 + embed() + + Full customization can be done by passing a :class:`Config` in as the + config argument. + """ + config = kwargs.get('config') + header = kwargs.pop('header', u'') + compile_flags = kwargs.pop('compile_flags', None) + if config is None: + config = load_default_config() + config.InteractiveShellEmbed = config.TerminalInteractiveShell + kwargs['config'] = config + using = kwargs.get('using', 'sync') + if using : + kwargs['config'].update({'TerminalInteractiveShell':{'loop_runner':using, 'colors':'NoColor', 'autoawait': using!='sync'}}) + #save ps1/ps2 if defined + ps1 = None + ps2 = None + try: + ps1 = sys.ps1 + ps2 = sys.ps2 + except AttributeError: + pass + #save previous instance + saved_shell_instance = InteractiveShell._instance + if saved_shell_instance is not None: + cls = type(saved_shell_instance) + cls.clear_instance() + frame = sys._getframe(1) + shell = InteractiveShellEmbed.instance(_init_location_id='%s:%s' % ( + frame.f_code.co_filename, frame.f_lineno), **kwargs) + shell(header=header, stack_depth=2, compile_flags=compile_flags, + _call_location_id='%s:%s' % (frame.f_code.co_filename, frame.f_lineno)) + InteractiveShellEmbed.clear_instance() + #restore previous instance + if saved_shell_instance is not None: + cls = type(saved_shell_instance) + cls.clear_instance() + for subclass in cls._walk_mro(): + subclass._instance = saved_shell_instance + if ps1 is not None: + sys.ps1 = ps1 + sys.ps2 = ps2 diff --git a/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py b/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py index 4e35aadd61..f3b697b993 100644 --- a/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py +++ b/contrib/python/ipython/py3/IPython/terminal/interactiveshell.py @@ -1,648 +1,648 @@ -"""IPython terminal interface using prompt_toolkit""" - -import asyncio -import os -import sys -import warnings -from warnings import warn - -from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC -from IPython.utils import io -from IPython.utils.py3compat import input -from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title -from IPython.utils.process import abbrev_cwd -from traitlets import ( - Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union, - Any, validate -) - -from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode -from prompt_toolkit.filters import (HasFocus, Condition, IsDone) -from prompt_toolkit.formatted_text import PygmentsTokens -from prompt_toolkit.history import InMemoryHistory -from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor -from prompt_toolkit.output import ColorDepth -from prompt_toolkit.patch_stdout import patch_stdout -from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text -from prompt_toolkit.styles import DynamicStyle, merge_styles -from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict -from prompt_toolkit import __version__ as ptk_version - -from pygments.styles import get_style_by_name -from pygments.style import Style -from pygments.token import Token - -from .debugger import TerminalPdb, Pdb -from .magics import TerminalMagics -from .pt_inputhooks import get_inputhook_name_and_func -from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook -from .ptutils import IPythonPTCompleter, IPythonPTLexer -from .shortcuts import create_ipython_shortcuts - -DISPLAY_BANNER_DEPRECATED = object() -PTK3 = ptk_version.startswith('3.') - - -class _NoStyle(Style): pass - - - -_style_overrides_light_bg = { - Token.Prompt: '#0000ff', - Token.PromptNum: '#0000ee bold', - Token.OutPrompt: '#cc0000', - Token.OutPromptNum: '#bb0000 bold', -} - -_style_overrides_linux = { - Token.Prompt: '#00cc00', - Token.PromptNum: '#00bb00 bold', - Token.OutPrompt: '#cc0000', - Token.OutPromptNum: '#bb0000 bold', -} - -def get_default_editor(): - try: - return os.environ['EDITOR'] - except KeyError: - pass - except UnicodeError: - warn("$EDITOR environment variable is not pure ASCII. Using platform " - "default editor.") - - if os.name == 'posix': - return 'vi' # the only one guaranteed to be there! - else: - return 'notepad' # same in Windows! - -# conservatively check for tty -# overridden streams can result in things like: -# - sys.stdin = None -# - no isatty method -for _name in ('stdin', 'stdout', 'stderr'): - _stream = getattr(sys, _name) - if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty(): - _is_tty = False - break -else: - _is_tty = True - - -_use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) - -def black_reformat_handler(text_before_cursor): - import black - formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) - if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'): - formatted_text = formatted_text[:-1] - return formatted_text - - -class TerminalInteractiveShell(InteractiveShell): - mime_renderers = Dict().tag(config=True) - - space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' - 'to reserve for the tab completion menu, ' - 'search history, ...etc, the height of ' - 'these menus will at most this value. ' - 'Increase it is you prefer long and skinny ' - 'menus, decrease for short and wide.' - ).tag(config=True) - - pt_app = None - debugger_history = None - - simple_prompt = Bool(_use_simple_prompt, - help="""Use `raw_input` for the REPL, without completion and prompt colors. - - Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are: - IPython own testing machinery, and emacs inferior-shell integration through elpy. - - This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` - environment variable is set, or the current terminal is not a tty.""" - ).tag(config=True) - - @property - def debugger_cls(self): - return Pdb if self.simple_prompt else TerminalPdb - - confirm_exit = Bool(True, - help=""" - Set to confirm when you try to exit IPython with an EOF (Control-D - in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', - you can force a direct exit without any confirmation.""", - ).tag(config=True) - - editing_mode = Unicode('emacs', - help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", - ).tag(config=True) - - autoformatter = Unicode(None, - help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`", - allow_none=True - ).tag(config=True) - - mouse_support = Bool(False, - help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" - ).tag(config=True) - - # We don't load the list of styles for the help string, because loading - # Pygments plugins takes time and can cause unexpected errors. - highlighting_style = Union([Unicode('legacy'), Type(klass=Style)], - help="""The name or class of a Pygments style to use for syntax - highlighting. To see available styles, run `pygmentize -L styles`.""" - ).tag(config=True) - - @validate('editing_mode') - def _validate_editing_mode(self, proposal): - if proposal['value'].lower() == 'vim': - proposal['value']= 'vi' - elif proposal['value'].lower() == 'default': - proposal['value']= 'emacs' - - if hasattr(EditingMode, proposal['value'].upper()): - return proposal['value'].lower() - - return self.editing_mode - - - @observe('editing_mode') - def _editing_mode(self, change): - if self.pt_app: - self.pt_app.editing_mode = getattr(EditingMode, change.new.upper()) - - @observe('autoformatter') - def _autoformatter_changed(self, change): - formatter = change.new - if formatter is None: - self.reformat_handler = lambda x:x - elif formatter == 'black': - self.reformat_handler = black_reformat_handler - else: - raise ValueError - - @observe('highlighting_style') - @observe('colors') - def _highlighting_style_changed(self, change): - self.refresh_style() - - def refresh_style(self): - self._style = self._make_style_from_name_or_cls(self.highlighting_style) - - - highlighting_style_overrides = Dict( - help="Override highlighting format for specific tokens" - ).tag(config=True) - - true_color = Bool(False, - help=("Use 24bit colors instead of 256 colors in prompt highlighting. " - "If your terminal supports true color, the following command " - "should print 'TRUECOLOR' in orange: " - "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"") - ).tag(config=True) - - editor = Unicode(get_default_editor(), - help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." - ).tag(config=True) - - prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True) - - prompts = Instance(Prompts) - - @default('prompts') - def _prompts_default(self): - return self.prompts_class(self) - -# @observe('prompts') -# def _(self, change): -# self._update_layout() - - @default('displayhook_class') - def _displayhook_class_default(self): - return RichPromptDisplayHook - - term_title = Bool(True, - help="Automatically set the terminal title" - ).tag(config=True) - - term_title_format = Unicode("IPython: {cwd}", - help="Customize the terminal title format. This is a python format string. " + - "Available substitutions are: {cwd}." - ).tag(config=True) - - display_completions = Enum(('column', 'multicolumn','readlinelike'), - help= ( "Options for displaying tab completions, 'column', 'multicolumn', and " - "'readlinelike'. These options are for `prompt_toolkit`, see " - "`prompt_toolkit` documentation for more information." - ), - default_value='multicolumn').tag(config=True) - - highlight_matching_brackets = Bool(True, - help="Highlight matching brackets.", - ).tag(config=True) - - extra_open_editor_shortcuts = Bool(False, - help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. " - "This is in addition to the F2 binding, which is always enabled." - ).tag(config=True) - - handle_return = Any(None, - help="Provide an alternative handler to be called when the user presses " - "Return. This is an advanced option intended for debugging, which " - "may be changed or removed in later releases." - ).tag(config=True) - - enable_history_search = Bool(True, - help="Allows to enable/disable the prompt toolkit history search" - ).tag(config=True) - - prompt_includes_vi_mode = Bool(True, - help="Display the current vi mode (when using vi editing mode)." - ).tag(config=True) - - @observe('term_title') - def init_term_title(self, change=None): - # Enable or disable the terminal title. - if self.term_title: - toggle_set_term_title(True) - set_term_title(self.term_title_format.format(cwd=abbrev_cwd())) - else: - toggle_set_term_title(False) - - def restore_term_title(self): - if self.term_title: - restore_term_title() - - def init_display_formatter(self): - super(TerminalInteractiveShell, self).init_display_formatter() - # terminal only supports plain text - self.display_formatter.active_types = ['text/plain'] - # disable `_ipython_display_` - self.display_formatter.ipython_display_formatter.enabled = False - - def init_prompt_toolkit_cli(self): - if self.simple_prompt: - # Fall back to plain non-interactive output for tests. - # This is very limited. - def prompt(): - prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens()) - lines = [input(prompt_text)] - prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens()) - while self.check_complete('\n'.join(lines))[0] == 'incomplete': - lines.append( input(prompt_continuation) ) - return '\n'.join(lines) - self.prompt_for_code = prompt - return - - # Set up keyboard shortcuts - key_bindings = create_ipython_shortcuts(self) - - # Pre-populate history from IPython's history database - history = InMemoryHistory() - last_cell = u"" - for __, ___, cell in self.history_manager.get_tail(self.history_load_length, - include_latest=True): - # Ignore blank lines and consecutive duplicates - cell = cell.rstrip() - if cell and (cell != last_cell): - history.append_string(cell) - last_cell = cell - - self._style = self._make_style_from_name_or_cls(self.highlighting_style) - self.style = DynamicStyle(lambda: self._style) - - editing_mode = getattr(EditingMode, self.editing_mode.upper()) - - self.pt_loop = asyncio.new_event_loop() - self.pt_app = PromptSession( - editing_mode=editing_mode, - key_bindings=key_bindings, - history=history, - completer=IPythonPTCompleter(shell=self), - enable_history_search = self.enable_history_search, - style=self.style, - include_default_pygments_style=False, - mouse_support=self.mouse_support, - enable_open_in_editor=self.extra_open_editor_shortcuts, - color_depth=self.color_depth, - tempfile_suffix=".py", - **self._extra_prompt_options()) - - def _make_style_from_name_or_cls(self, name_or_cls): - """ - Small wrapper that make an IPython compatible style from a style name - - We need that to add style for prompt ... etc. - """ - style_overrides = {} - if name_or_cls == 'legacy': - legacy = self.colors.lower() - if legacy == 'linux': - style_cls = get_style_by_name('monokai') - style_overrides = _style_overrides_linux - elif legacy == 'lightbg': - style_overrides = _style_overrides_light_bg - style_cls = get_style_by_name('pastie') - elif legacy == 'neutral': - # The default theme needs to be visible on both a dark background - # and a light background, because we can't tell what the terminal - # looks like. These tweaks to the default theme help with that. - style_cls = get_style_by_name('default') - style_overrides.update({ - Token.Number: '#007700', - Token.Operator: 'noinherit', - Token.String: '#BB6622', - Token.Name.Function: '#2080D0', - Token.Name.Class: 'bold #2080D0', - Token.Name.Namespace: 'bold #2080D0', - Token.Name.Variable.Magic: '#ansiblue', - Token.Prompt: '#009900', - Token.PromptNum: '#ansibrightgreen bold', - Token.OutPrompt: '#990000', - Token.OutPromptNum: '#ansibrightred bold', - }) - - # Hack: Due to limited color support on the Windows console - # the prompt colors will be wrong without this - if os.name == 'nt': - style_overrides.update({ - Token.Prompt: '#ansidarkgreen', - Token.PromptNum: '#ansigreen bold', - Token.OutPrompt: '#ansidarkred', - Token.OutPromptNum: '#ansired bold', - }) - elif legacy =='nocolor': - style_cls=_NoStyle - style_overrides = {} - else : - raise ValueError('Got unknown colors: ', legacy) - else : - if isinstance(name_or_cls, str): - style_cls = get_style_by_name(name_or_cls) - else: - style_cls = name_or_cls - style_overrides = { - Token.Prompt: '#009900', - Token.PromptNum: '#ansibrightgreen bold', - Token.OutPrompt: '#990000', - Token.OutPromptNum: '#ansibrightred bold', - } - style_overrides.update(self.highlighting_style_overrides) - style = merge_styles([ - style_from_pygments_cls(style_cls), - style_from_pygments_dict(style_overrides), - ]) - - return style - - @property - def pt_complete_style(self): - return { - 'multicolumn': CompleteStyle.MULTI_COLUMN, - 'column': CompleteStyle.COLUMN, - 'readlinelike': CompleteStyle.READLINE_LIKE, - }[self.display_completions] - - @property - def color_depth(self): - return (ColorDepth.TRUE_COLOR if self.true_color else None) - - def _extra_prompt_options(self): - """ - Return the current layout option for the current Terminal InteractiveShell - """ - def get_message(): - return PygmentsTokens(self.prompts.in_prompt_tokens()) - - if self.editing_mode == 'emacs': - # with emacs mode the prompt is (usually) static, so we call only - # the function once. With VI mode it can toggle between [ins] and - # [nor] so we can't precompute. - # here I'm going to favor the default keybinding which almost - # everybody uses to decrease CPU usage. - # if we have issues with users with custom Prompts we can see how to - # work around this. - get_message = get_message() - - options = { - 'complete_in_thread': False, - 'lexer':IPythonPTLexer(), - 'reserve_space_for_menu':self.space_for_menu, - 'message': get_message, - 'prompt_continuation': ( - lambda width, lineno, is_soft_wrap: - PygmentsTokens(self.prompts.continuation_prompt_tokens(width))), - 'multiline': True, - 'complete_style': self.pt_complete_style, - - # Highlight matching brackets, but only when this setting is - # enabled, and only when the DEFAULT_BUFFER has the focus. - 'input_processors': [ConditionalProcessor( - processor=HighlightMatchingBracketProcessor(chars='[](){}'), - filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & - Condition(lambda: self.highlight_matching_brackets))], - } - if not PTK3: - options['inputhook'] = self.inputhook - - return options - - def prompt_for_code(self): - if self.rl_next_input: - default = self.rl_next_input - self.rl_next_input = None - else: - default = '' - - # In order to make sure that asyncio code written in the - # interactive shell doesn't interfere with the prompt, we run the - # prompt in a different event loop. - # If we don't do this, people could spawn coroutine with a - # while/true inside which will freeze the prompt. - - policy = asyncio.get_event_loop_policy() - try: - old_loop = policy.get_event_loop() - except RuntimeError: - # This happens when the the event loop is closed, - # e.g. by calling `asyncio.run()`. - old_loop = None - - policy.set_event_loop(self.pt_loop) - try: - with patch_stdout(raw=True): - text = self.pt_app.prompt( - default=default, - **self._extra_prompt_options()) - finally: - # Restore the original event loop. - if old_loop is not None: - policy.set_event_loop(old_loop) - - return text - - def enable_win_unicode_console(self): - # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows - # console by default, so WUC shouldn't be needed. - from warnings import warn - warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future", - DeprecationWarning, - stacklevel=2) - - def init_io(self): - if sys.platform not in {'win32', 'cli'}: - return - - import colorama - colorama.init() - - # For some reason we make these wrappers around stdout/stderr. - # For now, we need to reset them so all output gets coloured. - # https://github.com/ipython/ipython/issues/8669 - # io.std* are deprecated, but don't show our own deprecation warnings - # during initialization of the deprecated API. - with warnings.catch_warnings(): - warnings.simplefilter('ignore', DeprecationWarning) - io.stdout = io.IOStream(sys.stdout) - io.stderr = io.IOStream(sys.stderr) - - def init_magics(self): - super(TerminalInteractiveShell, self).init_magics() - self.register_magics(TerminalMagics) - - def init_alias(self): - # The parent class defines aliases that can be safely used with any - # frontend. - super(TerminalInteractiveShell, self).init_alias() - - # Now define aliases that only make sense on the terminal, because they - # need direct access to the console in a way that we can't emulate in - # GUI or web frontend - if os.name == 'posix': - for cmd in ('clear', 'more', 'less', 'man'): - self.alias_manager.soft_define_alias(cmd, cmd) - - - def __init__(self, *args, **kwargs): - super(TerminalInteractiveShell, self).__init__(*args, **kwargs) - self.init_prompt_toolkit_cli() - self.init_term_title() - self.keep_running = True - - self.debugger_history = InMemoryHistory() - - def ask_exit(self): - self.keep_running = False - - rl_next_input = None - - def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): - - if display_banner is not DISPLAY_BANNER_DEPRECATED: - warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) - - self.keep_running = True - while self.keep_running: - print(self.separate_in, end='') - - try: - code = self.prompt_for_code() - except EOFError: - if (not self.confirm_exit) \ - or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): - self.ask_exit() - - else: - if code: - self.run_cell(code, store_history=True) - - def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED): - # An extra layer of protection in case someone mashing Ctrl-C breaks - # out of our internal code. - if display_banner is not DISPLAY_BANNER_DEPRECATED: - warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) - while True: - try: - self.interact() - break - except KeyboardInterrupt as e: - print("\n%s escaped interact()\n" % type(e).__name__) - finally: - # An interrupt during the eventloop will mess up the - # internal state of the prompt_toolkit library. - # Stopping the eventloop fixes this, see - # https://github.com/ipython/ipython/pull/9867 - if hasattr(self, '_eventloop'): - self._eventloop.stop() - - self.restore_term_title() - - - _inputhook = None - def inputhook(self, context): - if self._inputhook is not None: - self._inputhook(context) - - active_eventloop = None - def enable_gui(self, gui=None): - if gui and (gui != 'inline') : - self.active_eventloop, self._inputhook =\ - get_inputhook_name_and_func(gui) - else: - self.active_eventloop = self._inputhook = None - - # For prompt_toolkit 3.0. We have to create an asyncio event loop with - # this inputhook. - if PTK3: - import asyncio - from prompt_toolkit.eventloop import new_eventloop_with_inputhook - - if gui == 'asyncio': - # When we integrate the asyncio event loop, run the UI in the - # same event loop as the rest of the code. don't use an actual - # input hook. (Asyncio is not made for nesting event loops.) - self.pt_loop = asyncio.get_event_loop() - - elif self._inputhook: - # If an inputhook was set, create a new asyncio event loop with - # this inputhook for the prompt. - self.pt_loop = new_eventloop_with_inputhook(self._inputhook) - else: - # When there's no inputhook, run the prompt in a separate - # asyncio event loop. - self.pt_loop = asyncio.new_event_loop() - - # Run !system commands directly, not through pipes, so terminal programs - # work correctly. - system = InteractiveShell.system_raw - - def auto_rewrite_input(self, cmd): - """Overridden from the parent class to use fancy rewriting prompt""" - if not self.show_rewritten_input: - return - - tokens = self.prompts.rewrite_prompt_tokens() - if self.pt_app: - print_formatted_text(PygmentsTokens(tokens), end='', - style=self.pt_app.app.style) - print(cmd) - else: - prompt = ''.join(s for t, s in tokens) - print(prompt, cmd, sep='') - - _prompts_before = None - def switch_doctest_mode(self, mode): - """Switch prompts to classic for %doctest_mode""" - if mode: - self._prompts_before = self.prompts - self.prompts = ClassicPrompts(self) - elif self._prompts_before: - self.prompts = self._prompts_before - self._prompts_before = None -# self._update_layout() - - -InteractiveShellABC.register(TerminalInteractiveShell) - -if __name__ == '__main__': - TerminalInteractiveShell.instance().interact() +"""IPython terminal interface using prompt_toolkit""" + +import asyncio +import os +import sys +import warnings +from warnings import warn + +from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC +from IPython.utils import io +from IPython.utils.py3compat import input +from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title +from IPython.utils.process import abbrev_cwd +from traitlets import ( + Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union, + Any, validate +) + +from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode +from prompt_toolkit.filters import (HasFocus, Condition, IsDone) +from prompt_toolkit.formatted_text import PygmentsTokens +from prompt_toolkit.history import InMemoryHistory +from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor +from prompt_toolkit.output import ColorDepth +from prompt_toolkit.patch_stdout import patch_stdout +from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text +from prompt_toolkit.styles import DynamicStyle, merge_styles +from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict +from prompt_toolkit import __version__ as ptk_version + +from pygments.styles import get_style_by_name +from pygments.style import Style +from pygments.token import Token + +from .debugger import TerminalPdb, Pdb +from .magics import TerminalMagics +from .pt_inputhooks import get_inputhook_name_and_func +from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook +from .ptutils import IPythonPTCompleter, IPythonPTLexer +from .shortcuts import create_ipython_shortcuts + +DISPLAY_BANNER_DEPRECATED = object() +PTK3 = ptk_version.startswith('3.') + + +class _NoStyle(Style): pass + + + +_style_overrides_light_bg = { + Token.Prompt: '#0000ff', + Token.PromptNum: '#0000ee bold', + Token.OutPrompt: '#cc0000', + Token.OutPromptNum: '#bb0000 bold', +} + +_style_overrides_linux = { + Token.Prompt: '#00cc00', + Token.PromptNum: '#00bb00 bold', + Token.OutPrompt: '#cc0000', + Token.OutPromptNum: '#bb0000 bold', +} + +def get_default_editor(): + try: + return os.environ['EDITOR'] + except KeyError: + pass + except UnicodeError: + warn("$EDITOR environment variable is not pure ASCII. Using platform " + "default editor.") + + if os.name == 'posix': + return 'vi' # the only one guaranteed to be there! + else: + return 'notepad' # same in Windows! + +# conservatively check for tty +# overridden streams can result in things like: +# - sys.stdin = None +# - no isatty method +for _name in ('stdin', 'stdout', 'stderr'): + _stream = getattr(sys, _name) + if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty(): + _is_tty = False + break +else: + _is_tty = True + + +_use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty) + +def black_reformat_handler(text_before_cursor): + import black + formatted_text = black.format_str(text_before_cursor, mode=black.FileMode()) + if not text_before_cursor.endswith('\n') and formatted_text.endswith('\n'): + formatted_text = formatted_text[:-1] + return formatted_text + + +class TerminalInteractiveShell(InteractiveShell): + mime_renderers = Dict().tag(config=True) + + space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' + 'to reserve for the tab completion menu, ' + 'search history, ...etc, the height of ' + 'these menus will at most this value. ' + 'Increase it is you prefer long and skinny ' + 'menus, decrease for short and wide.' + ).tag(config=True) + + pt_app = None + debugger_history = None + + simple_prompt = Bool(_use_simple_prompt, + help="""Use `raw_input` for the REPL, without completion and prompt colors. + + Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are: + IPython own testing machinery, and emacs inferior-shell integration through elpy. + + This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` + environment variable is set, or the current terminal is not a tty.""" + ).tag(config=True) + + @property + def debugger_cls(self): + return Pdb if self.simple_prompt else TerminalPdb + + confirm_exit = Bool(True, + help=""" + Set to confirm when you try to exit IPython with an EOF (Control-D + in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', + you can force a direct exit without any confirmation.""", + ).tag(config=True) + + editing_mode = Unicode('emacs', + help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", + ).tag(config=True) + + autoformatter = Unicode(None, + help="Autoformatter to reformat Terminal code. Can be `'black'` or `None`", + allow_none=True + ).tag(config=True) + + mouse_support = Bool(False, + help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)" + ).tag(config=True) + + # We don't load the list of styles for the help string, because loading + # Pygments plugins takes time and can cause unexpected errors. + highlighting_style = Union([Unicode('legacy'), Type(klass=Style)], + help="""The name or class of a Pygments style to use for syntax + highlighting. To see available styles, run `pygmentize -L styles`.""" + ).tag(config=True) + + @validate('editing_mode') + def _validate_editing_mode(self, proposal): + if proposal['value'].lower() == 'vim': + proposal['value']= 'vi' + elif proposal['value'].lower() == 'default': + proposal['value']= 'emacs' + + if hasattr(EditingMode, proposal['value'].upper()): + return proposal['value'].lower() + + return self.editing_mode + + + @observe('editing_mode') + def _editing_mode(self, change): + if self.pt_app: + self.pt_app.editing_mode = getattr(EditingMode, change.new.upper()) + + @observe('autoformatter') + def _autoformatter_changed(self, change): + formatter = change.new + if formatter is None: + self.reformat_handler = lambda x:x + elif formatter == 'black': + self.reformat_handler = black_reformat_handler + else: + raise ValueError + + @observe('highlighting_style') + @observe('colors') + def _highlighting_style_changed(self, change): + self.refresh_style() + + def refresh_style(self): + self._style = self._make_style_from_name_or_cls(self.highlighting_style) + + + highlighting_style_overrides = Dict( + help="Override highlighting format for specific tokens" + ).tag(config=True) + + true_color = Bool(False, + help=("Use 24bit colors instead of 256 colors in prompt highlighting. " + "If your terminal supports true color, the following command " + "should print 'TRUECOLOR' in orange: " + "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"") + ).tag(config=True) + + editor = Unicode(get_default_editor(), + help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." + ).tag(config=True) + + prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True) + + prompts = Instance(Prompts) + + @default('prompts') + def _prompts_default(self): + return self.prompts_class(self) + +# @observe('prompts') +# def _(self, change): +# self._update_layout() + + @default('displayhook_class') + def _displayhook_class_default(self): + return RichPromptDisplayHook + + term_title = Bool(True, + help="Automatically set the terminal title" + ).tag(config=True) + + term_title_format = Unicode("IPython: {cwd}", + help="Customize the terminal title format. This is a python format string. " + + "Available substitutions are: {cwd}." + ).tag(config=True) + + display_completions = Enum(('column', 'multicolumn','readlinelike'), + help= ( "Options for displaying tab completions, 'column', 'multicolumn', and " + "'readlinelike'. These options are for `prompt_toolkit`, see " + "`prompt_toolkit` documentation for more information." + ), + default_value='multicolumn').tag(config=True) + + highlight_matching_brackets = Bool(True, + help="Highlight matching brackets.", + ).tag(config=True) + + extra_open_editor_shortcuts = Bool(False, + help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. " + "This is in addition to the F2 binding, which is always enabled." + ).tag(config=True) + + handle_return = Any(None, + help="Provide an alternative handler to be called when the user presses " + "Return. This is an advanced option intended for debugging, which " + "may be changed or removed in later releases." + ).tag(config=True) + + enable_history_search = Bool(True, + help="Allows to enable/disable the prompt toolkit history search" + ).tag(config=True) + + prompt_includes_vi_mode = Bool(True, + help="Display the current vi mode (when using vi editing mode)." + ).tag(config=True) + + @observe('term_title') + def init_term_title(self, change=None): + # Enable or disable the terminal title. + if self.term_title: + toggle_set_term_title(True) + set_term_title(self.term_title_format.format(cwd=abbrev_cwd())) + else: + toggle_set_term_title(False) + + def restore_term_title(self): + if self.term_title: + restore_term_title() + + def init_display_formatter(self): + super(TerminalInteractiveShell, self).init_display_formatter() + # terminal only supports plain text + self.display_formatter.active_types = ['text/plain'] + # disable `_ipython_display_` + self.display_formatter.ipython_display_formatter.enabled = False + + def init_prompt_toolkit_cli(self): + if self.simple_prompt: + # Fall back to plain non-interactive output for tests. + # This is very limited. + def prompt(): + prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens()) + lines = [input(prompt_text)] + prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens()) + while self.check_complete('\n'.join(lines))[0] == 'incomplete': + lines.append( input(prompt_continuation) ) + return '\n'.join(lines) + self.prompt_for_code = prompt + return + + # Set up keyboard shortcuts + key_bindings = create_ipython_shortcuts(self) + + # Pre-populate history from IPython's history database + history = InMemoryHistory() + last_cell = u"" + for __, ___, cell in self.history_manager.get_tail(self.history_load_length, + include_latest=True): + # Ignore blank lines and consecutive duplicates + cell = cell.rstrip() + if cell and (cell != last_cell): + history.append_string(cell) + last_cell = cell + + self._style = self._make_style_from_name_or_cls(self.highlighting_style) + self.style = DynamicStyle(lambda: self._style) + + editing_mode = getattr(EditingMode, self.editing_mode.upper()) + + self.pt_loop = asyncio.new_event_loop() + self.pt_app = PromptSession( + editing_mode=editing_mode, + key_bindings=key_bindings, + history=history, + completer=IPythonPTCompleter(shell=self), + enable_history_search = self.enable_history_search, + style=self.style, + include_default_pygments_style=False, + mouse_support=self.mouse_support, + enable_open_in_editor=self.extra_open_editor_shortcuts, + color_depth=self.color_depth, + tempfile_suffix=".py", + **self._extra_prompt_options()) + + def _make_style_from_name_or_cls(self, name_or_cls): + """ + Small wrapper that make an IPython compatible style from a style name + + We need that to add style for prompt ... etc. + """ + style_overrides = {} + if name_or_cls == 'legacy': + legacy = self.colors.lower() + if legacy == 'linux': + style_cls = get_style_by_name('monokai') + style_overrides = _style_overrides_linux + elif legacy == 'lightbg': + style_overrides = _style_overrides_light_bg + style_cls = get_style_by_name('pastie') + elif legacy == 'neutral': + # The default theme needs to be visible on both a dark background + # and a light background, because we can't tell what the terminal + # looks like. These tweaks to the default theme help with that. + style_cls = get_style_by_name('default') + style_overrides.update({ + Token.Number: '#007700', + Token.Operator: 'noinherit', + Token.String: '#BB6622', + Token.Name.Function: '#2080D0', + Token.Name.Class: 'bold #2080D0', + Token.Name.Namespace: 'bold #2080D0', + Token.Name.Variable.Magic: '#ansiblue', + Token.Prompt: '#009900', + Token.PromptNum: '#ansibrightgreen bold', + Token.OutPrompt: '#990000', + Token.OutPromptNum: '#ansibrightred bold', + }) + + # Hack: Due to limited color support on the Windows console + # the prompt colors will be wrong without this + if os.name == 'nt': + style_overrides.update({ + Token.Prompt: '#ansidarkgreen', + Token.PromptNum: '#ansigreen bold', + Token.OutPrompt: '#ansidarkred', + Token.OutPromptNum: '#ansired bold', + }) + elif legacy =='nocolor': + style_cls=_NoStyle + style_overrides = {} + else : + raise ValueError('Got unknown colors: ', legacy) + else : + if isinstance(name_or_cls, str): + style_cls = get_style_by_name(name_or_cls) + else: + style_cls = name_or_cls + style_overrides = { + Token.Prompt: '#009900', + Token.PromptNum: '#ansibrightgreen bold', + Token.OutPrompt: '#990000', + Token.OutPromptNum: '#ansibrightred bold', + } + style_overrides.update(self.highlighting_style_overrides) + style = merge_styles([ + style_from_pygments_cls(style_cls), + style_from_pygments_dict(style_overrides), + ]) + + return style + + @property + def pt_complete_style(self): + return { + 'multicolumn': CompleteStyle.MULTI_COLUMN, + 'column': CompleteStyle.COLUMN, + 'readlinelike': CompleteStyle.READLINE_LIKE, + }[self.display_completions] + + @property + def color_depth(self): + return (ColorDepth.TRUE_COLOR if self.true_color else None) + + def _extra_prompt_options(self): + """ + Return the current layout option for the current Terminal InteractiveShell + """ + def get_message(): + return PygmentsTokens(self.prompts.in_prompt_tokens()) + + if self.editing_mode == 'emacs': + # with emacs mode the prompt is (usually) static, so we call only + # the function once. With VI mode it can toggle between [ins] and + # [nor] so we can't precompute. + # here I'm going to favor the default keybinding which almost + # everybody uses to decrease CPU usage. + # if we have issues with users with custom Prompts we can see how to + # work around this. + get_message = get_message() + + options = { + 'complete_in_thread': False, + 'lexer':IPythonPTLexer(), + 'reserve_space_for_menu':self.space_for_menu, + 'message': get_message, + 'prompt_continuation': ( + lambda width, lineno, is_soft_wrap: + PygmentsTokens(self.prompts.continuation_prompt_tokens(width))), + 'multiline': True, + 'complete_style': self.pt_complete_style, + + # Highlight matching brackets, but only when this setting is + # enabled, and only when the DEFAULT_BUFFER has the focus. + 'input_processors': [ConditionalProcessor( + processor=HighlightMatchingBracketProcessor(chars='[](){}'), + filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & + Condition(lambda: self.highlight_matching_brackets))], + } + if not PTK3: + options['inputhook'] = self.inputhook + + return options + + def prompt_for_code(self): + if self.rl_next_input: + default = self.rl_next_input + self.rl_next_input = None + else: + default = '' + + # In order to make sure that asyncio code written in the + # interactive shell doesn't interfere with the prompt, we run the + # prompt in a different event loop. + # If we don't do this, people could spawn coroutine with a + # while/true inside which will freeze the prompt. + + policy = asyncio.get_event_loop_policy() + try: + old_loop = policy.get_event_loop() + except RuntimeError: + # This happens when the the event loop is closed, + # e.g. by calling `asyncio.run()`. + old_loop = None + + policy.set_event_loop(self.pt_loop) + try: + with patch_stdout(raw=True): + text = self.pt_app.prompt( + default=default, + **self._extra_prompt_options()) + finally: + # Restore the original event loop. + if old_loop is not None: + policy.set_event_loop(old_loop) + + return text + + def enable_win_unicode_console(self): + # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows + # console by default, so WUC shouldn't be needed. + from warnings import warn + warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future", + DeprecationWarning, + stacklevel=2) + + def init_io(self): + if sys.platform not in {'win32', 'cli'}: + return + + import colorama + colorama.init() + + # For some reason we make these wrappers around stdout/stderr. + # For now, we need to reset them so all output gets coloured. + # https://github.com/ipython/ipython/issues/8669 + # io.std* are deprecated, but don't show our own deprecation warnings + # during initialization of the deprecated API. + with warnings.catch_warnings(): + warnings.simplefilter('ignore', DeprecationWarning) + io.stdout = io.IOStream(sys.stdout) + io.stderr = io.IOStream(sys.stderr) + + def init_magics(self): + super(TerminalInteractiveShell, self).init_magics() + self.register_magics(TerminalMagics) + + def init_alias(self): + # The parent class defines aliases that can be safely used with any + # frontend. + super(TerminalInteractiveShell, self).init_alias() + + # Now define aliases that only make sense on the terminal, because they + # need direct access to the console in a way that we can't emulate in + # GUI or web frontend + if os.name == 'posix': + for cmd in ('clear', 'more', 'less', 'man'): + self.alias_manager.soft_define_alias(cmd, cmd) + + + def __init__(self, *args, **kwargs): + super(TerminalInteractiveShell, self).__init__(*args, **kwargs) + self.init_prompt_toolkit_cli() + self.init_term_title() + self.keep_running = True + + self.debugger_history = InMemoryHistory() + + def ask_exit(self): + self.keep_running = False + + rl_next_input = None + + def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): + + if display_banner is not DISPLAY_BANNER_DEPRECATED: + warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) + + self.keep_running = True + while self.keep_running: + print(self.separate_in, end='') + + try: + code = self.prompt_for_code() + except EOFError: + if (not self.confirm_exit) \ + or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): + self.ask_exit() + + else: + if code: + self.run_cell(code, store_history=True) + + def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED): + # An extra layer of protection in case someone mashing Ctrl-C breaks + # out of our internal code. + if display_banner is not DISPLAY_BANNER_DEPRECATED: + warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) + while True: + try: + self.interact() + break + except KeyboardInterrupt as e: + print("\n%s escaped interact()\n" % type(e).__name__) + finally: + # An interrupt during the eventloop will mess up the + # internal state of the prompt_toolkit library. + # Stopping the eventloop fixes this, see + # https://github.com/ipython/ipython/pull/9867 + if hasattr(self, '_eventloop'): + self._eventloop.stop() + + self.restore_term_title() + + + _inputhook = None + def inputhook(self, context): + if self._inputhook is not None: + self._inputhook(context) + + active_eventloop = None + def enable_gui(self, gui=None): + if gui and (gui != 'inline') : + self.active_eventloop, self._inputhook =\ + get_inputhook_name_and_func(gui) + else: + self.active_eventloop = self._inputhook = None + + # For prompt_toolkit 3.0. We have to create an asyncio event loop with + # this inputhook. + if PTK3: + import asyncio + from prompt_toolkit.eventloop import new_eventloop_with_inputhook + + if gui == 'asyncio': + # When we integrate the asyncio event loop, run the UI in the + # same event loop as the rest of the code. don't use an actual + # input hook. (Asyncio is not made for nesting event loops.) + self.pt_loop = asyncio.get_event_loop() + + elif self._inputhook: + # If an inputhook was set, create a new asyncio event loop with + # this inputhook for the prompt. + self.pt_loop = new_eventloop_with_inputhook(self._inputhook) + else: + # When there's no inputhook, run the prompt in a separate + # asyncio event loop. + self.pt_loop = asyncio.new_event_loop() + + # Run !system commands directly, not through pipes, so terminal programs + # work correctly. + system = InteractiveShell.system_raw + + def auto_rewrite_input(self, cmd): + """Overridden from the parent class to use fancy rewriting prompt""" + if not self.show_rewritten_input: + return + + tokens = self.prompts.rewrite_prompt_tokens() + if self.pt_app: + print_formatted_text(PygmentsTokens(tokens), end='', + style=self.pt_app.app.style) + print(cmd) + else: + prompt = ''.join(s for t, s in tokens) + print(prompt, cmd, sep='') + + _prompts_before = None + def switch_doctest_mode(self, mode): + """Switch prompts to classic for %doctest_mode""" + if mode: + self._prompts_before = self.prompts + self.prompts = ClassicPrompts(self) + elif self._prompts_before: + self.prompts = self._prompts_before + self._prompts_before = None +# self._update_layout() + + +InteractiveShellABC.register(TerminalInteractiveShell) + +if __name__ == '__main__': + TerminalInteractiveShell.instance().interact() diff --git a/contrib/python/ipython/py3/IPython/terminal/ipapp.py b/contrib/python/ipython/py3/IPython/terminal/ipapp.py index defe3e79fa..180cec1158 100644 --- a/contrib/python/ipython/py3/IPython/terminal/ipapp.py +++ b/contrib/python/ipython/py3/IPython/terminal/ipapp.py @@ -1,380 +1,380 @@ -#!/usr/bin/env python -# encoding: utf-8 -""" -The :class:`~IPython.core.application.Application` object for the command -line :command:`ipython` program. -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - -import logging -import os -import sys -import warnings - -from traitlets.config.loader import Config -from traitlets.config.application import boolean_flag, catch_config_error -from IPython.core import release -from IPython.core import usage -from IPython.core.completer import IPCompleter -from IPython.core.crashhandler import CrashHandler -from IPython.core.formatters import PlainTextFormatter -from IPython.core.history import HistoryManager -from IPython.core.application import ( - ProfileDir, BaseIPythonApplication, base_flags, base_aliases -) -from IPython.core.magics import ( - ScriptMagics, LoggingMagics -) -from IPython.core.shellapp import ( - InteractiveShellApp, shell_flags, shell_aliases -) -from IPython.extensions.storemagic import StoreMagics -from .interactiveshell import TerminalInteractiveShell -from IPython.paths import get_ipython_dir -from traitlets import ( - Bool, List, default, observe, Type -) - -#----------------------------------------------------------------------------- -# Globals, utilities and helpers -#----------------------------------------------------------------------------- - -_examples = """ -ipython --matplotlib # enable matplotlib integration -ipython --matplotlib=qt # enable matplotlib integration with qt4 backend - -ipython --log-level=DEBUG # set logging to DEBUG -ipython --profile=foo # start with profile foo - -ipython profile create foo # create profile foo w/ default config files -ipython help profile # show the help for the profile subcmd - -ipython locate # print the path to the IPython directory -ipython locate profile foo # print the path to the directory for profile `foo` -""" - -#----------------------------------------------------------------------------- -# Crash handler for this application -#----------------------------------------------------------------------------- - -class IPAppCrashHandler(CrashHandler): - """sys.excepthook for IPython itself, leaves a detailed report on disk.""" - - def __init__(self, app): - contact_name = release.author - contact_email = release.author_email - bug_tracker = 'https://github.com/ipython/ipython/issues' - super(IPAppCrashHandler,self).__init__( - app, contact_name, contact_email, bug_tracker - ) - - def make_report(self,traceback): - """Return a string containing a crash report.""" - - sec_sep = self.section_sep - # Start with parent report - report = [super(IPAppCrashHandler, self).make_report(traceback)] - # Add interactive-specific info we may have - rpt_add = report.append - try: - rpt_add(sec_sep+"History of session input:") - for line in self.app.shell.user_ns['_ih']: - rpt_add(line) - rpt_add('\n*** Last line of input (may not be in above history):\n') - rpt_add(self.app.shell._last_input_line+'\n') - except: - pass - - return ''.join(report) - -#----------------------------------------------------------------------------- -# Aliases and Flags -#----------------------------------------------------------------------------- -flags = dict(base_flags) -flags.update(shell_flags) -frontend_flags = {} -addflag = lambda *args: frontend_flags.update(boolean_flag(*args)) -addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax', - 'Turn on auto editing of files with syntax errors.', - 'Turn off auto editing of files with syntax errors.' -) -addflag('simple-prompt', 'TerminalInteractiveShell.simple_prompt', - "Force simple minimal prompt using `raw_input`", - "Use a rich interactive prompt with prompt_toolkit", -) - -addflag('banner', 'TerminalIPythonApp.display_banner', - "Display a banner upon starting IPython.", - "Don't display a banner upon starting IPython." -) -addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit', - """Set to confirm when you try to exit IPython with an EOF (Control-D - in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', - you can force a direct exit without any confirmation.""", - "Don't prompt the user when exiting." -) -addflag('term-title', 'TerminalInteractiveShell.term_title', - "Enable auto setting the terminal title.", - "Disable auto setting the terminal title." -) -classic_config = Config() -classic_config.InteractiveShell.cache_size = 0 -classic_config.PlainTextFormatter.pprint = False -classic_config.TerminalInteractiveShell.prompts_class='IPython.terminal.prompts.ClassicPrompts' -classic_config.InteractiveShell.separate_in = '' -classic_config.InteractiveShell.separate_out = '' -classic_config.InteractiveShell.separate_out2 = '' -classic_config.InteractiveShell.colors = 'NoColor' -classic_config.InteractiveShell.xmode = 'Plain' - -frontend_flags['classic']=( - classic_config, - "Gives IPython a similar feel to the classic Python prompt." -) -# # log doesn't make so much sense this way anymore -# paa('--log','-l', -# action='store_true', dest='InteractiveShell.logstart', -# help="Start logging to the default log file (./ipython_log.py).") -# -# # quick is harder to implement -frontend_flags['quick']=( - {'TerminalIPythonApp' : {'quick' : True}}, - "Enable quick startup with no config files." -) - -frontend_flags['i'] = ( - {'TerminalIPythonApp' : {'force_interact' : True}}, - """If running code from the command line, become interactive afterwards. - It is often useful to follow this with `--` to treat remaining flags as - script arguments. - """ -) -flags.update(frontend_flags) - -aliases = dict(base_aliases) -aliases.update(shell_aliases) - -#----------------------------------------------------------------------------- -# Main classes and functions -#----------------------------------------------------------------------------- - - -class LocateIPythonApp(BaseIPythonApplication): - description = """print the path to the IPython dir""" - subcommands = dict( - profile=('IPython.core.profileapp.ProfileLocate', - "print the path to an IPython profile directory", - ), - ) - def start(self): - if self.subapp is not None: - return self.subapp.start() - else: - print(self.ipython_dir) - - -class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): - name = u'ipython' - description = usage.cl_usage - crash_handler_class = IPAppCrashHandler - examples = _examples - - flags = flags - aliases = aliases - classes = List() - - interactive_shell_class = Type( - klass=object, # use default_value otherwise which only allow subclasses. - default_value=TerminalInteractiveShell, - help="Class to use to instantiate the TerminalInteractiveShell object. Useful for custom Frontends" - ).tag(config=True) - - @default('classes') - def _classes_default(self): - """This has to be in a method, for TerminalIPythonApp to be available.""" - return [ - InteractiveShellApp, # ShellApp comes before TerminalApp, because - self.__class__, # it will also affect subclasses (e.g. QtConsole) - TerminalInteractiveShell, - HistoryManager, - ProfileDir, - PlainTextFormatter, - IPCompleter, - ScriptMagics, - LoggingMagics, - StoreMagics, - ] - - deprecated_subcommands = dict( - qtconsole=('qtconsole.qtconsoleapp.JupyterQtConsoleApp', - """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter Qt Console.""" - ), - notebook=('notebook.notebookapp.NotebookApp', - """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter HTML Notebook Server.""" - ), - console=('jupyter_console.app.ZMQTerminalIPythonApp', - """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter terminal-based Console.""" - ), - nbconvert=('nbconvert.nbconvertapp.NbConvertApp', - "DEPRECATED, Will be removed in IPython 6.0 : Convert notebooks to/from other formats." - ), - trust=('nbformat.sign.TrustNotebookApp', - "DEPRECATED, Will be removed in IPython 6.0 : Sign notebooks to trust their potentially unsafe contents at load." - ), - kernelspec=('jupyter_client.kernelspecapp.KernelSpecApp', - "DEPRECATED, Will be removed in IPython 6.0 : Manage Jupyter kernel specifications." - ), - ) - subcommands = dict( - profile = ("IPython.core.profileapp.ProfileApp", - "Create and manage IPython profiles." - ), - kernel = ("ipykernel.kernelapp.IPKernelApp", - "Start a kernel without an attached frontend." - ), - locate=('IPython.terminal.ipapp.LocateIPythonApp', - LocateIPythonApp.description - ), - history=('IPython.core.historyapp.HistoryApp', - "Manage the IPython history database." - ), - ) - deprecated_subcommands['install-nbextension'] = ( - "notebook.nbextensions.InstallNBExtensionApp", - "DEPRECATED, Will be removed in IPython 6.0 : Install Jupyter notebook extension files" - ) - subcommands.update(deprecated_subcommands) - - # *do* autocreate requested profile, but don't create the config file. - auto_create=Bool(True) - # configurables - quick = Bool(False, - help="""Start IPython quickly by skipping the loading of config files.""" - ).tag(config=True) - @observe('quick') - def _quick_changed(self, change): - if change['new']: - self.load_config_file = lambda *a, **kw: None - - display_banner = Bool(True, - help="Whether to display a banner upon starting IPython." - ).tag(config=True) - - # if there is code of files to run from the cmd line, don't interact - # unless the --i flag (App.force_interact) is true. - force_interact = Bool(False, - help="""If a command or file is given via the command-line, - e.g. 'ipython foo.py', start an interactive shell after executing the - file or command.""" - ).tag(config=True) - @observe('force_interact') - def _force_interact_changed(self, change): - if change['new']: - self.interact = True - - @observe('file_to_run', 'code_to_run', 'module_to_run') - def _file_to_run_changed(self, change): - new = change['new'] - if new: - self.something_to_run = True - if new and not self.force_interact: - self.interact = False - - # internal, not-configurable - something_to_run=Bool(False) - - def parse_command_line(self, argv=None): - """override to allow old '-pylab' flag with deprecation warning""" - - argv = sys.argv[1:] if argv is None else argv - - if '-pylab' in argv: - # deprecated `-pylab` given, - # warn and transform into current syntax - argv = argv[:] # copy, don't clobber - idx = argv.index('-pylab') - warnings.warn("`-pylab` flag has been deprecated.\n" - " Use `--matplotlib <backend>` and import pylab manually.") - argv[idx] = '--pylab' - - return super(TerminalIPythonApp, self).parse_command_line(argv) - - @catch_config_error - def initialize(self, argv=None): - """Do actions after construct, but before starting the app.""" - super(TerminalIPythonApp, self).initialize(argv) - if self.subapp is not None: - # don't bother initializing further, starting subapp - return - # print self.extra_args - if self.extra_args and not self.something_to_run: - self.file_to_run = self.extra_args[0] - self.init_path() - # create the shell - self.init_shell() - # and draw the banner - self.init_banner() - # Now a variety of things that happen after the banner is printed. - self.init_gui_pylab() - self.init_extensions() - self.init_code() - - def init_shell(self): - """initialize the InteractiveShell instance""" - # Create an InteractiveShell instance. - # shell.display_banner should always be False for the terminal - # based app, because we call shell.show_banner() by hand below - # so the banner shows *before* all extension loading stuff. - self.shell = self.interactive_shell_class.instance(parent=self, - profile_dir=self.profile_dir, - ipython_dir=self.ipython_dir, user_ns=self.user_ns) - self.shell.configurables.append(self) - - def init_banner(self): - """optionally display the banner""" - if self.display_banner and self.interact: - self.shell.show_banner() - # Make sure there is a space below the banner. - if self.log_level <= logging.INFO: print() - - def _pylab_changed(self, name, old, new): - """Replace --pylab='inline' with --pylab='auto'""" - if new == 'inline': - warnings.warn("'inline' not available as pylab backend, " - "using 'auto' instead.") - self.pylab = 'auto' - - def start(self): - if self.subapp is not None: - return self.subapp.start() - # perform any prexec steps: - if self.interact: - self.log.debug("Starting IPython's mainloop...") - self.shell.mainloop() - else: - self.log.debug("IPython not interactive...") - if not self.shell.last_execution_succeeded: - sys.exit(1) - -def load_default_config(ipython_dir=None): - """Load the default config file from the default ipython_dir. - - This is useful for embedded shells. - """ - if ipython_dir is None: - ipython_dir = get_ipython_dir() - - profile_dir = os.path.join(ipython_dir, 'profile_default') - app = TerminalIPythonApp() - app.config_file_paths.append(profile_dir) - app.load_config_file() - return app.config - -launch_new_instance = TerminalIPythonApp.launch_instance - - -if __name__ == '__main__': - launch_new_instance() +#!/usr/bin/env python +# encoding: utf-8 +""" +The :class:`~IPython.core.application.Application` object for the command +line :command:`ipython` program. +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +import logging +import os +import sys +import warnings + +from traitlets.config.loader import Config +from traitlets.config.application import boolean_flag, catch_config_error +from IPython.core import release +from IPython.core import usage +from IPython.core.completer import IPCompleter +from IPython.core.crashhandler import CrashHandler +from IPython.core.formatters import PlainTextFormatter +from IPython.core.history import HistoryManager +from IPython.core.application import ( + ProfileDir, BaseIPythonApplication, base_flags, base_aliases +) +from IPython.core.magics import ( + ScriptMagics, LoggingMagics +) +from IPython.core.shellapp import ( + InteractiveShellApp, shell_flags, shell_aliases +) +from IPython.extensions.storemagic import StoreMagics +from .interactiveshell import TerminalInteractiveShell +from IPython.paths import get_ipython_dir +from traitlets import ( + Bool, List, default, observe, Type +) + +#----------------------------------------------------------------------------- +# Globals, utilities and helpers +#----------------------------------------------------------------------------- + +_examples = """ +ipython --matplotlib # enable matplotlib integration +ipython --matplotlib=qt # enable matplotlib integration with qt4 backend + +ipython --log-level=DEBUG # set logging to DEBUG +ipython --profile=foo # start with profile foo + +ipython profile create foo # create profile foo w/ default config files +ipython help profile # show the help for the profile subcmd + +ipython locate # print the path to the IPython directory +ipython locate profile foo # print the path to the directory for profile `foo` +""" + +#----------------------------------------------------------------------------- +# Crash handler for this application +#----------------------------------------------------------------------------- + +class IPAppCrashHandler(CrashHandler): + """sys.excepthook for IPython itself, leaves a detailed report on disk.""" + + def __init__(self, app): + contact_name = release.author + contact_email = release.author_email + bug_tracker = 'https://github.com/ipython/ipython/issues' + super(IPAppCrashHandler,self).__init__( + app, contact_name, contact_email, bug_tracker + ) + + def make_report(self,traceback): + """Return a string containing a crash report.""" + + sec_sep = self.section_sep + # Start with parent report + report = [super(IPAppCrashHandler, self).make_report(traceback)] + # Add interactive-specific info we may have + rpt_add = report.append + try: + rpt_add(sec_sep+"History of session input:") + for line in self.app.shell.user_ns['_ih']: + rpt_add(line) + rpt_add('\n*** Last line of input (may not be in above history):\n') + rpt_add(self.app.shell._last_input_line+'\n') + except: + pass + + return ''.join(report) + +#----------------------------------------------------------------------------- +# Aliases and Flags +#----------------------------------------------------------------------------- +flags = dict(base_flags) +flags.update(shell_flags) +frontend_flags = {} +addflag = lambda *args: frontend_flags.update(boolean_flag(*args)) +addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax', + 'Turn on auto editing of files with syntax errors.', + 'Turn off auto editing of files with syntax errors.' +) +addflag('simple-prompt', 'TerminalInteractiveShell.simple_prompt', + "Force simple minimal prompt using `raw_input`", + "Use a rich interactive prompt with prompt_toolkit", +) + +addflag('banner', 'TerminalIPythonApp.display_banner', + "Display a banner upon starting IPython.", + "Don't display a banner upon starting IPython." +) +addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit', + """Set to confirm when you try to exit IPython with an EOF (Control-D + in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', + you can force a direct exit without any confirmation.""", + "Don't prompt the user when exiting." +) +addflag('term-title', 'TerminalInteractiveShell.term_title', + "Enable auto setting the terminal title.", + "Disable auto setting the terminal title." +) +classic_config = Config() +classic_config.InteractiveShell.cache_size = 0 +classic_config.PlainTextFormatter.pprint = False +classic_config.TerminalInteractiveShell.prompts_class='IPython.terminal.prompts.ClassicPrompts' +classic_config.InteractiveShell.separate_in = '' +classic_config.InteractiveShell.separate_out = '' +classic_config.InteractiveShell.separate_out2 = '' +classic_config.InteractiveShell.colors = 'NoColor' +classic_config.InteractiveShell.xmode = 'Plain' + +frontend_flags['classic']=( + classic_config, + "Gives IPython a similar feel to the classic Python prompt." +) +# # log doesn't make so much sense this way anymore +# paa('--log','-l', +# action='store_true', dest='InteractiveShell.logstart', +# help="Start logging to the default log file (./ipython_log.py).") +# +# # quick is harder to implement +frontend_flags['quick']=( + {'TerminalIPythonApp' : {'quick' : True}}, + "Enable quick startup with no config files." +) + +frontend_flags['i'] = ( + {'TerminalIPythonApp' : {'force_interact' : True}}, + """If running code from the command line, become interactive afterwards. + It is often useful to follow this with `--` to treat remaining flags as + script arguments. + """ +) +flags.update(frontend_flags) + +aliases = dict(base_aliases) +aliases.update(shell_aliases) + +#----------------------------------------------------------------------------- +# Main classes and functions +#----------------------------------------------------------------------------- + + +class LocateIPythonApp(BaseIPythonApplication): + description = """print the path to the IPython dir""" + subcommands = dict( + profile=('IPython.core.profileapp.ProfileLocate', + "print the path to an IPython profile directory", + ), + ) + def start(self): + if self.subapp is not None: + return self.subapp.start() + else: + print(self.ipython_dir) + + +class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp): + name = u'ipython' + description = usage.cl_usage + crash_handler_class = IPAppCrashHandler + examples = _examples + + flags = flags + aliases = aliases + classes = List() + + interactive_shell_class = Type( + klass=object, # use default_value otherwise which only allow subclasses. + default_value=TerminalInteractiveShell, + help="Class to use to instantiate the TerminalInteractiveShell object. Useful for custom Frontends" + ).tag(config=True) + + @default('classes') + def _classes_default(self): + """This has to be in a method, for TerminalIPythonApp to be available.""" + return [ + InteractiveShellApp, # ShellApp comes before TerminalApp, because + self.__class__, # it will also affect subclasses (e.g. QtConsole) + TerminalInteractiveShell, + HistoryManager, + ProfileDir, + PlainTextFormatter, + IPCompleter, + ScriptMagics, + LoggingMagics, + StoreMagics, + ] + + deprecated_subcommands = dict( + qtconsole=('qtconsole.qtconsoleapp.JupyterQtConsoleApp', + """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter Qt Console.""" + ), + notebook=('notebook.notebookapp.NotebookApp', + """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter HTML Notebook Server.""" + ), + console=('jupyter_console.app.ZMQTerminalIPythonApp', + """DEPRECATED, Will be removed in IPython 6.0 : Launch the Jupyter terminal-based Console.""" + ), + nbconvert=('nbconvert.nbconvertapp.NbConvertApp', + "DEPRECATED, Will be removed in IPython 6.0 : Convert notebooks to/from other formats." + ), + trust=('nbformat.sign.TrustNotebookApp', + "DEPRECATED, Will be removed in IPython 6.0 : Sign notebooks to trust their potentially unsafe contents at load." + ), + kernelspec=('jupyter_client.kernelspecapp.KernelSpecApp', + "DEPRECATED, Will be removed in IPython 6.0 : Manage Jupyter kernel specifications." + ), + ) + subcommands = dict( + profile = ("IPython.core.profileapp.ProfileApp", + "Create and manage IPython profiles." + ), + kernel = ("ipykernel.kernelapp.IPKernelApp", + "Start a kernel without an attached frontend." + ), + locate=('IPython.terminal.ipapp.LocateIPythonApp', + LocateIPythonApp.description + ), + history=('IPython.core.historyapp.HistoryApp', + "Manage the IPython history database." + ), + ) + deprecated_subcommands['install-nbextension'] = ( + "notebook.nbextensions.InstallNBExtensionApp", + "DEPRECATED, Will be removed in IPython 6.0 : Install Jupyter notebook extension files" + ) + subcommands.update(deprecated_subcommands) + + # *do* autocreate requested profile, but don't create the config file. + auto_create=Bool(True) + # configurables + quick = Bool(False, + help="""Start IPython quickly by skipping the loading of config files.""" + ).tag(config=True) + @observe('quick') + def _quick_changed(self, change): + if change['new']: + self.load_config_file = lambda *a, **kw: None + + display_banner = Bool(True, + help="Whether to display a banner upon starting IPython." + ).tag(config=True) + + # if there is code of files to run from the cmd line, don't interact + # unless the --i flag (App.force_interact) is true. + force_interact = Bool(False, + help="""If a command or file is given via the command-line, + e.g. 'ipython foo.py', start an interactive shell after executing the + file or command.""" + ).tag(config=True) + @observe('force_interact') + def _force_interact_changed(self, change): + if change['new']: + self.interact = True + + @observe('file_to_run', 'code_to_run', 'module_to_run') + def _file_to_run_changed(self, change): + new = change['new'] + if new: + self.something_to_run = True + if new and not self.force_interact: + self.interact = False + + # internal, not-configurable + something_to_run=Bool(False) + + def parse_command_line(self, argv=None): + """override to allow old '-pylab' flag with deprecation warning""" + + argv = sys.argv[1:] if argv is None else argv + + if '-pylab' in argv: + # deprecated `-pylab` given, + # warn and transform into current syntax + argv = argv[:] # copy, don't clobber + idx = argv.index('-pylab') + warnings.warn("`-pylab` flag has been deprecated.\n" + " Use `--matplotlib <backend>` and import pylab manually.") + argv[idx] = '--pylab' + + return super(TerminalIPythonApp, self).parse_command_line(argv) + + @catch_config_error + def initialize(self, argv=None): + """Do actions after construct, but before starting the app.""" + super(TerminalIPythonApp, self).initialize(argv) + if self.subapp is not None: + # don't bother initializing further, starting subapp + return + # print self.extra_args + if self.extra_args and not self.something_to_run: + self.file_to_run = self.extra_args[0] + self.init_path() + # create the shell + self.init_shell() + # and draw the banner + self.init_banner() + # Now a variety of things that happen after the banner is printed. + self.init_gui_pylab() + self.init_extensions() + self.init_code() + + def init_shell(self): + """initialize the InteractiveShell instance""" + # Create an InteractiveShell instance. + # shell.display_banner should always be False for the terminal + # based app, because we call shell.show_banner() by hand below + # so the banner shows *before* all extension loading stuff. + self.shell = self.interactive_shell_class.instance(parent=self, + profile_dir=self.profile_dir, + ipython_dir=self.ipython_dir, user_ns=self.user_ns) + self.shell.configurables.append(self) + + def init_banner(self): + """optionally display the banner""" + if self.display_banner and self.interact: + self.shell.show_banner() + # Make sure there is a space below the banner. + if self.log_level <= logging.INFO: print() + + def _pylab_changed(self, name, old, new): + """Replace --pylab='inline' with --pylab='auto'""" + if new == 'inline': + warnings.warn("'inline' not available as pylab backend, " + "using 'auto' instead.") + self.pylab = 'auto' + + def start(self): + if self.subapp is not None: + return self.subapp.start() + # perform any prexec steps: + if self.interact: + self.log.debug("Starting IPython's mainloop...") + self.shell.mainloop() + else: + self.log.debug("IPython not interactive...") + if not self.shell.last_execution_succeeded: + sys.exit(1) + +def load_default_config(ipython_dir=None): + """Load the default config file from the default ipython_dir. + + This is useful for embedded shells. + """ + if ipython_dir is None: + ipython_dir = get_ipython_dir() + + profile_dir = os.path.join(ipython_dir, 'profile_default') + app = TerminalIPythonApp() + app.config_file_paths.append(profile_dir) + app.load_config_file() + return app.config + +launch_new_instance = TerminalIPythonApp.launch_instance + + +if __name__ == '__main__': + launch_new_instance() diff --git a/contrib/python/ipython/py3/IPython/terminal/magics.py b/contrib/python/ipython/py3/IPython/terminal/magics.py index 42231c3f80..d6f9c1d701 100644 --- a/contrib/python/ipython/py3/IPython/terminal/magics.py +++ b/contrib/python/ipython/py3/IPython/terminal/magics.py @@ -1,213 +1,213 @@ -"""Extra magics for terminal use.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - - -from logging import error -import os -import sys - -from IPython.core.error import TryNext, UsageError -from IPython.core.magic import Magics, magics_class, line_magic -from IPython.lib.clipboard import ClipboardEmpty -from IPython.utils.text import SList, strip_email_quotes -from IPython.utils import py3compat - -def get_pasted_lines(sentinel, l_input=py3compat.input, quiet=False): - """ Yield pasted lines until the user enters the given sentinel value. - """ - if not quiet: - print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \ - % sentinel) - prompt = ":" - else: - prompt = "" - while True: - try: - l = l_input(prompt) - if l == sentinel: - return - else: - yield l - except EOFError: - print('<EOF>') - return - - -@magics_class -class TerminalMagics(Magics): - def __init__(self, shell): - super(TerminalMagics, self).__init__(shell) - - def store_or_execute(self, block, name): - """ Execute a block, or store it in a variable, per the user's request. - """ - if name: - # If storing it for further editing - self.shell.user_ns[name] = SList(block.splitlines()) - print("Block assigned to '%s'" % name) - else: - b = self.preclean_input(block) - self.shell.user_ns['pasted_block'] = b - self.shell.using_paste_magics = True - try: - self.shell.run_cell(b) - finally: - self.shell.using_paste_magics = False - - def preclean_input(self, block): - lines = block.splitlines() - while lines and not lines[0].strip(): - lines = lines[1:] - return strip_email_quotes('\n'.join(lines)) - - def rerun_pasted(self, name='pasted_block'): - """ Rerun a previously pasted command. - """ - b = self.shell.user_ns.get(name) - - # Sanity checks - if b is None: - raise UsageError('No previous pasted block available') - if not isinstance(b, str): - raise UsageError( - "Variable 'pasted_block' is not a string, can't execute") - - print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b))) - self.shell.run_cell(b) - - @line_magic - def autoindent(self, parameter_s = ''): - """Toggle autoindent on/off (deprecated)""" - self.shell.set_autoindent() - print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent]) - - @line_magic - def cpaste(self, parameter_s=''): - """Paste & execute a pre-formatted code block from clipboard. - - You must terminate the block with '--' (two minus-signs) or Ctrl-D - alone on the line. You can also provide your own sentinel with '%paste - -s %%' ('%%' is the new sentinel for this operation). - - The block is dedented prior to execution to enable execution of method - definitions. '>' and '+' characters at the beginning of a line are - ignored, to allow pasting directly from e-mails, diff files and - doctests (the '...' continuation prompt is also stripped). The - executed block is also assigned to variable named 'pasted_block' for - later editing with '%edit pasted_block'. - - You can also pass a variable name as an argument, e.g. '%cpaste foo'. - This assigns the pasted block to variable 'foo' as string, without - dedenting or executing it (preceding >>> and + is still stripped) - - '%cpaste -r' re-executes the block previously entered by cpaste. - '%cpaste -q' suppresses any additional output messages. - - Do not be alarmed by garbled output on Windows (it's a readline bug). - Just press enter and type -- (and press enter again) and the block - will be what was just pasted. - - Shell escapes are not supported (yet). - - See also - -------- - paste: automatically pull code from clipboard. - - Examples - -------- - :: - - In [8]: %cpaste - Pasting code; enter '--' alone on the line to stop. - :>>> a = ["world!", "Hello"] - :>>> print(" ".join(sorted(a))) - :-- - Hello world! - - :: - In [8]: %cpaste - Pasting code; enter '--' alone on the line to stop. - :>>> %alias_magic t timeit - :>>> %t -n1 pass - :-- - Created `%t` as an alias for `%timeit`. - Created `%%t` as an alias for `%%timeit`. - 354 ns ± 224 ns per loop (mean ± std. dev. of 7 runs, 1 loop each) - """ - opts, name = self.parse_options(parameter_s, 'rqs:', mode='string') - if 'r' in opts: - self.rerun_pasted() - return - - quiet = ('q' in opts) - - sentinel = opts.get('s', u'--') - block = '\n'.join(get_pasted_lines(sentinel, quiet=quiet)) - self.store_or_execute(block, name) - - @line_magic - def paste(self, parameter_s=''): - """Paste & execute a pre-formatted code block from clipboard. - - The text is pulled directly from the clipboard without user - intervention and printed back on the screen before execution (unless - the -q flag is given to force quiet mode). - - The block is dedented prior to execution to enable execution of method - definitions. '>' and '+' characters at the beginning of a line are - ignored, to allow pasting directly from e-mails, diff files and - doctests (the '...' continuation prompt is also stripped). The - executed block is also assigned to variable named 'pasted_block' for - later editing with '%edit pasted_block'. - - You can also pass a variable name as an argument, e.g. '%paste foo'. - This assigns the pasted block to variable 'foo' as string, without - executing it (preceding >>> and + is still stripped). - - Options: - - -r: re-executes the block previously entered by cpaste. - - -q: quiet mode: do not echo the pasted text back to the terminal. - - IPython statements (magics, shell escapes) are not supported (yet). - - See also - -------- - cpaste: manually paste code into terminal until you mark its end. - """ - opts, name = self.parse_options(parameter_s, 'rq', mode='string') - if 'r' in opts: - self.rerun_pasted() - return - try: - block = self.shell.hooks.clipboard_get() - except TryNext as clipboard_exc: - message = getattr(clipboard_exc, 'args') - if message: - error(message[0]) - else: - error('Could not get text from the clipboard.') - return - except ClipboardEmpty: - raise UsageError("The clipboard appears to be empty") - - # By default, echo back to terminal unless quiet mode is requested - if 'q' not in opts: - write = self.shell.write - write(self.shell.pycolorize(block)) - if not block.endswith('\n'): - write('\n') - write("## -- End pasted text --\n") - - self.store_or_execute(block, name) - - # Class-level: add a '%cls' magic only on Windows - if sys.platform == 'win32': - @line_magic - def cls(self, s): - """Clear screen. - """ - os.system("cls") +"""Extra magics for terminal use.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + +from logging import error +import os +import sys + +from IPython.core.error import TryNext, UsageError +from IPython.core.magic import Magics, magics_class, line_magic +from IPython.lib.clipboard import ClipboardEmpty +from IPython.utils.text import SList, strip_email_quotes +from IPython.utils import py3compat + +def get_pasted_lines(sentinel, l_input=py3compat.input, quiet=False): + """ Yield pasted lines until the user enters the given sentinel value. + """ + if not quiet: + print("Pasting code; enter '%s' alone on the line to stop or use Ctrl-D." \ + % sentinel) + prompt = ":" + else: + prompt = "" + while True: + try: + l = l_input(prompt) + if l == sentinel: + return + else: + yield l + except EOFError: + print('<EOF>') + return + + +@magics_class +class TerminalMagics(Magics): + def __init__(self, shell): + super(TerminalMagics, self).__init__(shell) + + def store_or_execute(self, block, name): + """ Execute a block, or store it in a variable, per the user's request. + """ + if name: + # If storing it for further editing + self.shell.user_ns[name] = SList(block.splitlines()) + print("Block assigned to '%s'" % name) + else: + b = self.preclean_input(block) + self.shell.user_ns['pasted_block'] = b + self.shell.using_paste_magics = True + try: + self.shell.run_cell(b) + finally: + self.shell.using_paste_magics = False + + def preclean_input(self, block): + lines = block.splitlines() + while lines and not lines[0].strip(): + lines = lines[1:] + return strip_email_quotes('\n'.join(lines)) + + def rerun_pasted(self, name='pasted_block'): + """ Rerun a previously pasted command. + """ + b = self.shell.user_ns.get(name) + + # Sanity checks + if b is None: + raise UsageError('No previous pasted block available') + if not isinstance(b, str): + raise UsageError( + "Variable 'pasted_block' is not a string, can't execute") + + print("Re-executing '%s...' (%d chars)"% (b.split('\n',1)[0], len(b))) + self.shell.run_cell(b) + + @line_magic + def autoindent(self, parameter_s = ''): + """Toggle autoindent on/off (deprecated)""" + self.shell.set_autoindent() + print("Automatic indentation is:",['OFF','ON'][self.shell.autoindent]) + + @line_magic + def cpaste(self, parameter_s=''): + """Paste & execute a pre-formatted code block from clipboard. + + You must terminate the block with '--' (two minus-signs) or Ctrl-D + alone on the line. You can also provide your own sentinel with '%paste + -s %%' ('%%' is the new sentinel for this operation). + + The block is dedented prior to execution to enable execution of method + definitions. '>' and '+' characters at the beginning of a line are + ignored, to allow pasting directly from e-mails, diff files and + doctests (the '...' continuation prompt is also stripped). The + executed block is also assigned to variable named 'pasted_block' for + later editing with '%edit pasted_block'. + + You can also pass a variable name as an argument, e.g. '%cpaste foo'. + This assigns the pasted block to variable 'foo' as string, without + dedenting or executing it (preceding >>> and + is still stripped) + + '%cpaste -r' re-executes the block previously entered by cpaste. + '%cpaste -q' suppresses any additional output messages. + + Do not be alarmed by garbled output on Windows (it's a readline bug). + Just press enter and type -- (and press enter again) and the block + will be what was just pasted. + + Shell escapes are not supported (yet). + + See also + -------- + paste: automatically pull code from clipboard. + + Examples + -------- + :: + + In [8]: %cpaste + Pasting code; enter '--' alone on the line to stop. + :>>> a = ["world!", "Hello"] + :>>> print(" ".join(sorted(a))) + :-- + Hello world! + + :: + In [8]: %cpaste + Pasting code; enter '--' alone on the line to stop. + :>>> %alias_magic t timeit + :>>> %t -n1 pass + :-- + Created `%t` as an alias for `%timeit`. + Created `%%t` as an alias for `%%timeit`. + 354 ns ± 224 ns per loop (mean ± std. dev. of 7 runs, 1 loop each) + """ + opts, name = self.parse_options(parameter_s, 'rqs:', mode='string') + if 'r' in opts: + self.rerun_pasted() + return + + quiet = ('q' in opts) + + sentinel = opts.get('s', u'--') + block = '\n'.join(get_pasted_lines(sentinel, quiet=quiet)) + self.store_or_execute(block, name) + + @line_magic + def paste(self, parameter_s=''): + """Paste & execute a pre-formatted code block from clipboard. + + The text is pulled directly from the clipboard without user + intervention and printed back on the screen before execution (unless + the -q flag is given to force quiet mode). + + The block is dedented prior to execution to enable execution of method + definitions. '>' and '+' characters at the beginning of a line are + ignored, to allow pasting directly from e-mails, diff files and + doctests (the '...' continuation prompt is also stripped). The + executed block is also assigned to variable named 'pasted_block' for + later editing with '%edit pasted_block'. + + You can also pass a variable name as an argument, e.g. '%paste foo'. + This assigns the pasted block to variable 'foo' as string, without + executing it (preceding >>> and + is still stripped). + + Options: + + -r: re-executes the block previously entered by cpaste. + + -q: quiet mode: do not echo the pasted text back to the terminal. + + IPython statements (magics, shell escapes) are not supported (yet). + + See also + -------- + cpaste: manually paste code into terminal until you mark its end. + """ + opts, name = self.parse_options(parameter_s, 'rq', mode='string') + if 'r' in opts: + self.rerun_pasted() + return + try: + block = self.shell.hooks.clipboard_get() + except TryNext as clipboard_exc: + message = getattr(clipboard_exc, 'args') + if message: + error(message[0]) + else: + error('Could not get text from the clipboard.') + return + except ClipboardEmpty: + raise UsageError("The clipboard appears to be empty") + + # By default, echo back to terminal unless quiet mode is requested + if 'q' not in opts: + write = self.shell.write + write(self.shell.pycolorize(block)) + if not block.endswith('\n'): + write('\n') + write("## -- End pasted text --\n") + + self.store_or_execute(block, name) + + # Class-level: add a '%cls' magic only on Windows + if sys.platform == 'win32': + @line_magic + def cls(self, s): + """Clear screen. + """ + os.system("cls") diff --git a/contrib/python/ipython/py3/IPython/terminal/prompts.py b/contrib/python/ipython/py3/IPython/terminal/prompts.py index 3f5c07b980..55e90ccdd3 100644 --- a/contrib/python/ipython/py3/IPython/terminal/prompts.py +++ b/contrib/python/ipython/py3/IPython/terminal/prompts.py @@ -1,108 +1,108 @@ -"""Terminal input and output prompts.""" - -from pygments.token import Token -import sys - -from IPython.core.displayhook import DisplayHook - -from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens -from prompt_toolkit.shortcuts import print_formatted_text -from prompt_toolkit.enums import EditingMode - - -class Prompts(object): - def __init__(self, shell): - self.shell = shell - - def vi_mode(self): - if (getattr(self.shell.pt_app, 'editing_mode', None) == EditingMode.VI - and self.shell.prompt_includes_vi_mode): - mode = str(self.shell.pt_app.app.vi_state.input_mode) - if mode.startswith('InputMode.'): - mode = mode[10:13].lower() - elif mode.startswith('vi-'): - mode = mode[3:6] - return '['+mode+'] ' - return '' - - - def in_prompt_tokens(self): - return [ - (Token.Prompt, self.vi_mode() ), - (Token.Prompt, 'In ['), - (Token.PromptNum, str(self.shell.execution_count)), - (Token.Prompt, ']: '), - ] - - def _width(self): - return fragment_list_width(self.in_prompt_tokens()) - - def continuation_prompt_tokens(self, width=None): - if width is None: - width = self._width() - return [ - (Token.Prompt, (' ' * (width - 5)) + '...: '), - ] - - def rewrite_prompt_tokens(self): - width = self._width() - return [ - (Token.Prompt, ('-' * (width - 2)) + '> '), - ] - - def out_prompt_tokens(self): - return [ - (Token.OutPrompt, 'Out['), - (Token.OutPromptNum, str(self.shell.execution_count)), - (Token.OutPrompt, ']: '), - ] - -class ClassicPrompts(Prompts): - def in_prompt_tokens(self): - return [ - (Token.Prompt, '>>> '), - ] - - def continuation_prompt_tokens(self, width=None): - return [ - (Token.Prompt, '... ') - ] - - def rewrite_prompt_tokens(self): - return [] - - def out_prompt_tokens(self): - return [] - -class RichPromptDisplayHook(DisplayHook): - """Subclass of base display hook using coloured prompt""" - def write_output_prompt(self): - sys.stdout.write(self.shell.separate_out) - # If we're not displaying a prompt, it effectively ends with a newline, - # because the output will be left-aligned. - self.prompt_end_newline = True - - if self.do_full_cache: - tokens = self.shell.prompts.out_prompt_tokens() - prompt_txt = ''.join(s for t, s in tokens) - if prompt_txt and not prompt_txt.endswith('\n'): - # Ask for a newline before multiline output - self.prompt_end_newline = False - - if self.shell.pt_app: - print_formatted_text(PygmentsTokens(tokens), - style=self.shell.pt_app.app.style, end='', - ) - else: - sys.stdout.write(prompt_txt) - - def write_format_data(self, format_dict, md_dict=None) -> None: - if self.shell.mime_renderers: - - for mime, handler in self.shell.mime_renderers.items(): - if mime in format_dict: - handler(format_dict[mime], None) - return - - super().write_format_data(format_dict, md_dict) - +"""Terminal input and output prompts.""" + +from pygments.token import Token +import sys + +from IPython.core.displayhook import DisplayHook + +from prompt_toolkit.formatted_text import fragment_list_width, PygmentsTokens +from prompt_toolkit.shortcuts import print_formatted_text +from prompt_toolkit.enums import EditingMode + + +class Prompts(object): + def __init__(self, shell): + self.shell = shell + + def vi_mode(self): + if (getattr(self.shell.pt_app, 'editing_mode', None) == EditingMode.VI + and self.shell.prompt_includes_vi_mode): + mode = str(self.shell.pt_app.app.vi_state.input_mode) + if mode.startswith('InputMode.'): + mode = mode[10:13].lower() + elif mode.startswith('vi-'): + mode = mode[3:6] + return '['+mode+'] ' + return '' + + + def in_prompt_tokens(self): + return [ + (Token.Prompt, self.vi_mode() ), + (Token.Prompt, 'In ['), + (Token.PromptNum, str(self.shell.execution_count)), + (Token.Prompt, ']: '), + ] + + def _width(self): + return fragment_list_width(self.in_prompt_tokens()) + + def continuation_prompt_tokens(self, width=None): + if width is None: + width = self._width() + return [ + (Token.Prompt, (' ' * (width - 5)) + '...: '), + ] + + def rewrite_prompt_tokens(self): + width = self._width() + return [ + (Token.Prompt, ('-' * (width - 2)) + '> '), + ] + + def out_prompt_tokens(self): + return [ + (Token.OutPrompt, 'Out['), + (Token.OutPromptNum, str(self.shell.execution_count)), + (Token.OutPrompt, ']: '), + ] + +class ClassicPrompts(Prompts): + def in_prompt_tokens(self): + return [ + (Token.Prompt, '>>> '), + ] + + def continuation_prompt_tokens(self, width=None): + return [ + (Token.Prompt, '... ') + ] + + def rewrite_prompt_tokens(self): + return [] + + def out_prompt_tokens(self): + return [] + +class RichPromptDisplayHook(DisplayHook): + """Subclass of base display hook using coloured prompt""" + def write_output_prompt(self): + sys.stdout.write(self.shell.separate_out) + # If we're not displaying a prompt, it effectively ends with a newline, + # because the output will be left-aligned. + self.prompt_end_newline = True + + if self.do_full_cache: + tokens = self.shell.prompts.out_prompt_tokens() + prompt_txt = ''.join(s for t, s in tokens) + if prompt_txt and not prompt_txt.endswith('\n'): + # Ask for a newline before multiline output + self.prompt_end_newline = False + + if self.shell.pt_app: + print_formatted_text(PygmentsTokens(tokens), + style=self.shell.pt_app.app.style, end='', + ) + else: + sys.stdout.write(prompt_txt) + + def write_format_data(self, format_dict, md_dict=None) -> None: + if self.shell.mime_renderers: + + for mime, handler in self.shell.mime_renderers.items(): + if mime in format_dict: + handler(format_dict[mime], None) + return + + super().write_format_data(format_dict, md_dict) + diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/__init__.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/__init__.py index 69ff0ba1e6..e41e26a5f5 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/__init__.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/__init__.py @@ -1,62 +1,62 @@ -import importlib -import os - -aliases = { - 'qt4': 'qt', - 'gtk2': 'gtk', -} - -backends = [ - "qt", - "qt4", - "qt5", - "qt6", - "gtk", - "gtk2", - "gtk3", - "gtk4", - "tk", - "wx", - "pyglet", - "glut", - "osx", - "asyncio", -] - -registered = {} - -def register(name, inputhook): - """Register the function *inputhook* as an event loop integration.""" - registered[name] = inputhook - - -class UnknownBackend(KeyError): - def __init__(self, name): - self.name = name - - def __str__(self): - return ("No event loop integration for {!r}. " - "Supported event loops are: {}").format(self.name, - ', '.join(backends + sorted(registered))) - - -def get_inputhook_name_and_func(gui): - if gui in registered: - return gui, registered[gui] - - if gui not in backends: - raise UnknownBackend(gui) - - if gui in aliases: - return get_inputhook_name_and_func(aliases[gui]) - - gui_mod = gui - if gui == "qt5": - os.environ["QT_API"] = "pyqt5" - gui_mod = "qt" - elif gui == "qt6": - os.environ["QT_API"] = "pyqt6" - gui_mod = "qt" - - mod = importlib.import_module('IPython.terminal.pt_inputhooks.'+gui_mod) - return gui, mod.inputhook +import importlib +import os + +aliases = { + 'qt4': 'qt', + 'gtk2': 'gtk', +} + +backends = [ + "qt", + "qt4", + "qt5", + "qt6", + "gtk", + "gtk2", + "gtk3", + "gtk4", + "tk", + "wx", + "pyglet", + "glut", + "osx", + "asyncio", +] + +registered = {} + +def register(name, inputhook): + """Register the function *inputhook* as an event loop integration.""" + registered[name] = inputhook + + +class UnknownBackend(KeyError): + def __init__(self, name): + self.name = name + + def __str__(self): + return ("No event loop integration for {!r}. " + "Supported event loops are: {}").format(self.name, + ', '.join(backends + sorted(registered))) + + +def get_inputhook_name_and_func(gui): + if gui in registered: + return gui, registered[gui] + + if gui not in backends: + raise UnknownBackend(gui) + + if gui in aliases: + return get_inputhook_name_and_func(aliases[gui]) + + gui_mod = gui + if gui == "qt5": + os.environ["QT_API"] = "pyqt5" + gui_mod = "qt" + elif gui == "qt6": + os.environ["QT_API"] = "pyqt6" + gui_mod = "qt" + + mod = importlib.import_module('IPython.terminal.pt_inputhooks.'+gui_mod) + return gui, mod.inputhook diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/asyncio.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/asyncio.py index 95cf194f86..44d480527d 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/asyncio.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/asyncio.py @@ -1,64 +1,64 @@ -""" -Inputhook for running the original asyncio event loop while we're waiting for -input. - -By default, in IPython, we run the prompt with a different asyncio event loop, -because otherwise we risk that people are freezing the prompt by scheduling bad -coroutines. E.g., a coroutine that does a while/true and never yield back -control to the loop. We can't cancel that. - -However, sometimes we want the asyncio loop to keep running while waiting for -a prompt. - -The following example will print the numbers from 1 to 10 above the prompt, -while we are waiting for input. (This works also because we use -prompt_toolkit`s `patch_stdout`):: - - In [1]: import asyncio - - In [2]: %gui asyncio - - In [3]: async def f(): - ...: for i in range(10): - ...: await asyncio.sleep(1) - ...: print(i) - - - In [4]: asyncio.ensure_future(f()) - -""" -import asyncio -from prompt_toolkit import __version__ as ptk_version - -PTK3 = ptk_version.startswith('3.') - - -# Keep reference to the original asyncio loop, because getting the event loop -# within the input hook would return the other loop. -loop = asyncio.get_event_loop() - - -def inputhook(context): - """ - Inputhook for asyncio event loop integration. - """ - # For prompt_toolkit 3.0, this input hook literally doesn't do anything. - # The event loop integration here is implemented in `interactiveshell.py` - # by running the prompt itself in the current asyncio loop. The main reason - # for this is that nesting asyncio event loops is unreliable. - if PTK3: - return - - # For prompt_toolkit 2.0, we can run the current asyncio event loop, - # because prompt_toolkit 2.0 uses a different event loop internally. - - def stop(): - loop.stop() - - fileno = context.fileno() - loop.add_reader(fileno, stop) - try: - loop.run_forever() - finally: - loop.remove_reader(fileno) - +""" +Inputhook for running the original asyncio event loop while we're waiting for +input. + +By default, in IPython, we run the prompt with a different asyncio event loop, +because otherwise we risk that people are freezing the prompt by scheduling bad +coroutines. E.g., a coroutine that does a while/true and never yield back +control to the loop. We can't cancel that. + +However, sometimes we want the asyncio loop to keep running while waiting for +a prompt. + +The following example will print the numbers from 1 to 10 above the prompt, +while we are waiting for input. (This works also because we use +prompt_toolkit`s `patch_stdout`):: + + In [1]: import asyncio + + In [2]: %gui asyncio + + In [3]: async def f(): + ...: for i in range(10): + ...: await asyncio.sleep(1) + ...: print(i) + + + In [4]: asyncio.ensure_future(f()) + +""" +import asyncio +from prompt_toolkit import __version__ as ptk_version + +PTK3 = ptk_version.startswith('3.') + + +# Keep reference to the original asyncio loop, because getting the event loop +# within the input hook would return the other loop. +loop = asyncio.get_event_loop() + + +def inputhook(context): + """ + Inputhook for asyncio event loop integration. + """ + # For prompt_toolkit 3.0, this input hook literally doesn't do anything. + # The event loop integration here is implemented in `interactiveshell.py` + # by running the prompt itself in the current asyncio loop. The main reason + # for this is that nesting asyncio event loops is unreliable. + if PTK3: + return + + # For prompt_toolkit 2.0, we can run the current asyncio event loop, + # because prompt_toolkit 2.0 uses a different event loop internally. + + def stop(): + loop.stop() + + fileno = context.fileno() + loop.add_reader(fileno, stop) + try: + loop.run_forever() + finally: + loop.remove_reader(fileno) + diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/glut.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/glut.py index f6d54a55b4..6715c77fd8 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/glut.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/glut.py @@ -1,140 +1,140 @@ -"""GLUT Input hook for interactive use with prompt_toolkit -""" - - -# GLUT is quite an old library and it is difficult to ensure proper -# integration within IPython since original GLUT does not allow to handle -# events one by one. Instead, it requires for the mainloop to be entered -# and never returned (there is not even a function to exit he -# mainloop). Fortunately, there are alternatives such as freeglut -# (available for linux and windows) and the OSX implementation gives -# access to a glutCheckLoop() function that blocks itself until a new -# event is received. This means we have to setup the idle callback to -# ensure we got at least one event that will unblock the function. -# -# Furthermore, it is not possible to install these handlers without a window -# being first created. We choose to make this window invisible. This means that -# display mode options are set at this level and user won't be able to change -# them later without modifying the code. This should probably be made available -# via IPython options system. - -import sys -import time -import signal -import OpenGL.GLUT as glut -import OpenGL.platform as platform -from timeit import default_timer as clock - -# Frame per second : 60 -# Should probably be an IPython option -glut_fps = 60 - -# Display mode : double buffeed + rgba + depth -# Should probably be an IPython option -glut_display_mode = (glut.GLUT_DOUBLE | - glut.GLUT_RGBA | - glut.GLUT_DEPTH) - -glutMainLoopEvent = None -if sys.platform == 'darwin': - try: - glutCheckLoop = platform.createBaseFunction( - 'glutCheckLoop', dll=platform.GLUT, resultType=None, - argTypes=[], - doc='glutCheckLoop( ) -> None', - argNames=(), - ) - except AttributeError: - raise RuntimeError( - '''Your glut implementation does not allow interactive sessions''' - '''Consider installing freeglut.''') - glutMainLoopEvent = glutCheckLoop -elif glut.HAVE_FREEGLUT: - glutMainLoopEvent = glut.glutMainLoopEvent -else: - raise RuntimeError( - '''Your glut implementation does not allow interactive sessions. ''' - '''Consider installing freeglut.''') - - -def glut_display(): - # Dummy display function - pass - -def glut_idle(): - # Dummy idle function - pass - -def glut_close(): - # Close function only hides the current window - glut.glutHideWindow() - glutMainLoopEvent() - -def glut_int_handler(signum, frame): - # Catch sigint and print the defaultipyt message - signal.signal(signal.SIGINT, signal.default_int_handler) - print('\nKeyboardInterrupt') - # Need to reprint the prompt at this stage - -# Initialisation code -glut.glutInit( sys.argv ) -glut.glutInitDisplayMode( glut_display_mode ) -# This is specific to freeglut -if bool(glut.glutSetOption): - glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE, - glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS ) -glut.glutCreateWindow( b'ipython' ) -glut.glutReshapeWindow( 1, 1 ) -glut.glutHideWindow( ) -glut.glutWMCloseFunc( glut_close ) -glut.glutDisplayFunc( glut_display ) -glut.glutIdleFunc( glut_idle ) - - -def inputhook(context): - """Run the pyglet event loop by processing pending events only. - - This keeps processing pending events until stdin is ready. After - processing all pending events, a call to time.sleep is inserted. This is - needed, otherwise, CPU usage is at 100%. This sleep time should be tuned - though for best performance. - """ - # We need to protect against a user pressing Control-C when IPython is - # idle and this is running. We trap KeyboardInterrupt and pass. - - signal.signal(signal.SIGINT, glut_int_handler) - - try: - t = clock() - - # Make sure the default window is set after a window has been closed - if glut.glutGetWindow() == 0: - glut.glutSetWindow( 1 ) - glutMainLoopEvent() - return 0 - - while not context.input_is_ready(): - glutMainLoopEvent() - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - except KeyboardInterrupt: - pass +"""GLUT Input hook for interactive use with prompt_toolkit +""" + + +# GLUT is quite an old library and it is difficult to ensure proper +# integration within IPython since original GLUT does not allow to handle +# events one by one. Instead, it requires for the mainloop to be entered +# and never returned (there is not even a function to exit he +# mainloop). Fortunately, there are alternatives such as freeglut +# (available for linux and windows) and the OSX implementation gives +# access to a glutCheckLoop() function that blocks itself until a new +# event is received. This means we have to setup the idle callback to +# ensure we got at least one event that will unblock the function. +# +# Furthermore, it is not possible to install these handlers without a window +# being first created. We choose to make this window invisible. This means that +# display mode options are set at this level and user won't be able to change +# them later without modifying the code. This should probably be made available +# via IPython options system. + +import sys +import time +import signal +import OpenGL.GLUT as glut +import OpenGL.platform as platform +from timeit import default_timer as clock + +# Frame per second : 60 +# Should probably be an IPython option +glut_fps = 60 + +# Display mode : double buffeed + rgba + depth +# Should probably be an IPython option +glut_display_mode = (glut.GLUT_DOUBLE | + glut.GLUT_RGBA | + glut.GLUT_DEPTH) + +glutMainLoopEvent = None +if sys.platform == 'darwin': + try: + glutCheckLoop = platform.createBaseFunction( + 'glutCheckLoop', dll=platform.GLUT, resultType=None, + argTypes=[], + doc='glutCheckLoop( ) -> None', + argNames=(), + ) + except AttributeError: + raise RuntimeError( + '''Your glut implementation does not allow interactive sessions''' + '''Consider installing freeglut.''') + glutMainLoopEvent = glutCheckLoop +elif glut.HAVE_FREEGLUT: + glutMainLoopEvent = glut.glutMainLoopEvent +else: + raise RuntimeError( + '''Your glut implementation does not allow interactive sessions. ''' + '''Consider installing freeglut.''') + + +def glut_display(): + # Dummy display function + pass + +def glut_idle(): + # Dummy idle function + pass + +def glut_close(): + # Close function only hides the current window + glut.glutHideWindow() + glutMainLoopEvent() + +def glut_int_handler(signum, frame): + # Catch sigint and print the defaultipyt message + signal.signal(signal.SIGINT, signal.default_int_handler) + print('\nKeyboardInterrupt') + # Need to reprint the prompt at this stage + +# Initialisation code +glut.glutInit( sys.argv ) +glut.glutInitDisplayMode( glut_display_mode ) +# This is specific to freeglut +if bool(glut.glutSetOption): + glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE, + glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS ) +glut.glutCreateWindow( b'ipython' ) +glut.glutReshapeWindow( 1, 1 ) +glut.glutHideWindow( ) +glut.glutWMCloseFunc( glut_close ) +glut.glutDisplayFunc( glut_display ) +glut.glutIdleFunc( glut_idle ) + + +def inputhook(context): + """Run the pyglet event loop by processing pending events only. + + This keeps processing pending events until stdin is ready. After + processing all pending events, a call to time.sleep is inserted. This is + needed, otherwise, CPU usage is at 100%. This sleep time should be tuned + though for best performance. + """ + # We need to protect against a user pressing Control-C when IPython is + # idle and this is running. We trap KeyboardInterrupt and pass. + + signal.signal(signal.SIGINT, glut_int_handler) + + try: + t = clock() + + # Make sure the default window is set after a window has been closed + if glut.glutGetWindow() == 0: + glut.glutSetWindow( 1 ) + glutMainLoopEvent() + return 0 + + while not context.input_is_ready(): + glutMainLoopEvent() + # We need to sleep at this point to keep the idle CPU load + # low. However, if sleep to long, GUI response is poor. As + # a compromise, we watch how often GUI events are being processed + # and switch between a short and long sleep time. Here are some + # stats useful in helping to tune this. + # time CPU load + # 0.001 13% + # 0.005 3% + # 0.01 1.5% + # 0.05 0.5% + used_time = clock() - t + if used_time > 10.0: + # print 'Sleep for 1 s' # dbg + time.sleep(1.0) + elif used_time > 0.1: + # Few GUI events coming in, so we can sleep longer + # print 'Sleep for 0.05 s' # dbg + time.sleep(0.05) + else: + # Many GUI events coming in, so sleep only very little + time.sleep(0.001) + except KeyboardInterrupt: + pass diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk.py index 6e246ba837..13ce1f7d1e 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk.py @@ -1,58 +1,58 @@ -# Code borrowed from python-prompt-toolkit examples -# https://github.com/jonathanslenders/python-prompt-toolkit/blob/77cdcfbc7f4b4c34a9d2f9a34d422d7152f16209/examples/inputhook.py - -# Copyright (c) 2014, Jonathan Slenders -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, this -# list of conditions and the following disclaimer in the documentation and/or -# other materials provided with the distribution. -# -# * Neither the name of the {organization} nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" -PyGTK input hook for prompt_toolkit. - -Listens on the pipe prompt_toolkit sets up for a notification that it should -return control to the terminal event loop. -""" - -import gtk, gobject - -# Enable threading in GTK. (Otherwise, GTK will keep the GIL.) -gtk.gdk.threads_init() - -def inputhook(context): - """ - When the eventloop of prompt-toolkit is idle, call this inputhook. - - This will run the GTK main loop until the file descriptor - `context.fileno()` becomes ready. - - :param context: An `InputHookContext` instance. - """ - def _main_quit(*a, **kw): - gtk.main_quit() - return False - - gobject.io_add_watch(context.fileno(), gobject.IO_IN, _main_quit) - gtk.main() +# Code borrowed from python-prompt-toolkit examples +# https://github.com/jonathanslenders/python-prompt-toolkit/blob/77cdcfbc7f4b4c34a9d2f9a34d422d7152f16209/examples/inputhook.py + +# Copyright (c) 2014, Jonathan Slenders +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# +# * Neither the name of the {organization} nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +PyGTK input hook for prompt_toolkit. + +Listens on the pipe prompt_toolkit sets up for a notification that it should +return control to the terminal event loop. +""" + +import gtk, gobject + +# Enable threading in GTK. (Otherwise, GTK will keep the GIL.) +gtk.gdk.threads_init() + +def inputhook(context): + """ + When the eventloop of prompt-toolkit is idle, call this inputhook. + + This will run the GTK main loop until the file descriptor + `context.fileno()` becomes ready. + + :param context: An `InputHookContext` instance. + """ + def _main_quit(*a, **kw): + gtk.main_quit() + return False + + gobject.io_add_watch(context.fileno(), gobject.IO_IN, _main_quit) + gtk.main() diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk3.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk3.py index ae82b4edaa..2c6243c072 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk3.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk3.py @@ -1,12 +1,12 @@ -"""prompt_toolkit input hook for GTK 3 -""" - -from gi.repository import Gtk, GLib - -def _main_quit(*args, **kwargs): - Gtk.main_quit() - return False - -def inputhook(context): - GLib.io_add_watch(context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) - Gtk.main() +"""prompt_toolkit input hook for GTK 3 +""" + +from gi.repository import Gtk, GLib + +def _main_quit(*args, **kwargs): + Gtk.main_quit() + return False + +def inputhook(context): + GLib.io_add_watch(context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, _main_quit) + Gtk.main() diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk4.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk4.py index 009fbf1212..38fce01f8c 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk4.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/gtk4.py @@ -1,27 +1,27 @@ -""" -prompt_toolkit input hook for GTK 4. -""" - -from gi.repository import GLib - - -class _InputHook: - def __init__(self, context): - self._quit = False - GLib.io_add_watch( - context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, self.quit - ) - - def quit(self, *args, **kwargs): - self._quit = True - return False - - def run(self): - context = GLib.MainContext.default() - while not self._quit: - context.iteration(True) - - -def inputhook(context): - hook = _InputHook(context) - hook.run() +""" +prompt_toolkit input hook for GTK 4. +""" + +from gi.repository import GLib + + +class _InputHook: + def __init__(self, context): + self._quit = False + GLib.io_add_watch( + context.fileno(), GLib.PRIORITY_DEFAULT, GLib.IO_IN, self.quit + ) + + def quit(self, *args, **kwargs): + self._quit = True + return False + + def run(self): + context = GLib.MainContext.default() + while not self._quit: + context.iteration(True) + + +def inputhook(context): + hook = _InputHook(context) + hook.run() diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/osx.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/osx.py index 80440196fb..4b48d0d1e3 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/osx.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/osx.py @@ -1,157 +1,157 @@ -"""Inputhook for OS X - -Calls NSApp / CoreFoundation APIs via ctypes. -""" - -# obj-c boilerplate from appnope, used under BSD 2-clause - -import ctypes -import ctypes.util -from threading import Event - -objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) - -void_p = ctypes.c_void_p - -objc.objc_getClass.restype = void_p -objc.sel_registerName.restype = void_p -objc.objc_msgSend.restype = void_p -objc.objc_msgSend.argtypes = [void_p, void_p] - -msg = objc.objc_msgSend - -def _utf8(s): - """ensure utf8 bytes""" - if not isinstance(s, bytes): - s = s.encode('utf8') - return s - -def n(name): - """create a selector name (for ObjC methods)""" - return objc.sel_registerName(_utf8(name)) - -def C(classname): - """get an ObjC Class by name""" - return objc.objc_getClass(_utf8(classname)) - -# end obj-c boilerplate from appnope - -# CoreFoundation C-API calls we will use: -CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) - -CFFileDescriptorCreate = CoreFoundation.CFFileDescriptorCreate -CFFileDescriptorCreate.restype = void_p -CFFileDescriptorCreate.argtypes = [void_p, ctypes.c_int, ctypes.c_bool, void_p, void_p] - -CFFileDescriptorGetNativeDescriptor = CoreFoundation.CFFileDescriptorGetNativeDescriptor -CFFileDescriptorGetNativeDescriptor.restype = ctypes.c_int -CFFileDescriptorGetNativeDescriptor.argtypes = [void_p] - -CFFileDescriptorEnableCallBacks = CoreFoundation.CFFileDescriptorEnableCallBacks -CFFileDescriptorEnableCallBacks.restype = None -CFFileDescriptorEnableCallBacks.argtypes = [void_p, ctypes.c_ulong] - -CFFileDescriptorCreateRunLoopSource = CoreFoundation.CFFileDescriptorCreateRunLoopSource -CFFileDescriptorCreateRunLoopSource.restype = void_p -CFFileDescriptorCreateRunLoopSource.argtypes = [void_p, void_p, void_p] - -CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent -CFRunLoopGetCurrent.restype = void_p - -CFRunLoopAddSource = CoreFoundation.CFRunLoopAddSource -CFRunLoopAddSource.restype = None -CFRunLoopAddSource.argtypes = [void_p, void_p, void_p] - -CFRelease = CoreFoundation.CFRelease -CFRelease.restype = None -CFRelease.argtypes = [void_p] - -CFFileDescriptorInvalidate = CoreFoundation.CFFileDescriptorInvalidate -CFFileDescriptorInvalidate.restype = None -CFFileDescriptorInvalidate.argtypes = [void_p] - -# From CFFileDescriptor.h -kCFFileDescriptorReadCallBack = 1 -kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes') - - -def _NSApp(): - """Return the global NSApplication instance (NSApp)""" - objc.objc_msgSend.argtypes = [void_p, void_p] - return msg(C('NSApplication'), n('sharedApplication')) - - -def _wake(NSApp): - """Wake the Application""" - objc.objc_msgSend.argtypes = [ - void_p, - void_p, - void_p, - void_p, - void_p, - void_p, - void_p, - void_p, - void_p, - void_p, - void_p, - ] - event = msg( - C("NSEvent"), - n( - "otherEventWithType:location:modifierFlags:" - "timestamp:windowNumber:context:subtype:data1:data2:" - ), - 15, # Type - 0, # location - 0, # flags - 0, # timestamp - 0, # window - None, # context - 0, # subtype - 0, # data1 - 0, # data2 - ) - objc.objc_msgSend.argtypes = [void_p, void_p, void_p, void_p] - msg(NSApp, n('postEvent:atStart:'), void_p(event), True) - - -_triggered = Event() - -def _input_callback(fdref, flags, info): - """Callback to fire when there's input to be read""" - _triggered.set() - CFFileDescriptorInvalidate(fdref) - CFRelease(fdref) - NSApp = _NSApp() - objc.objc_msgSend.argtypes = [void_p, void_p, void_p] - msg(NSApp, n('stop:'), NSApp) - _wake(NSApp) - -_c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p, void_p) -_c_input_callback = _c_callback_func_type(_input_callback) - - -def _stop_on_read(fd): - """Register callback to stop eventloop when there's data on fd""" - _triggered.clear() - fdref = CFFileDescriptorCreate(None, fd, False, _c_input_callback, None) - CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack) - source = CFFileDescriptorCreateRunLoopSource(None, fdref, 0) - loop = CFRunLoopGetCurrent() - CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes) - CFRelease(source) - - -def inputhook(context): - """Inputhook for Cocoa (NSApp)""" - NSApp = _NSApp() - _stop_on_read(context.fileno()) - objc.objc_msgSend.argtypes = [void_p, void_p] - msg(NSApp, n('run')) - if not _triggered.is_set(): - # app closed without firing callback, - # probably due to last window being closed. - # Run the loop manually in this case, - # since there may be events still to process (#9734) - CoreFoundation.CFRunLoopRun() +"""Inputhook for OS X + +Calls NSApp / CoreFoundation APIs via ctypes. +""" + +# obj-c boilerplate from appnope, used under BSD 2-clause + +import ctypes +import ctypes.util +from threading import Event + +objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) + +void_p = ctypes.c_void_p + +objc.objc_getClass.restype = void_p +objc.sel_registerName.restype = void_p +objc.objc_msgSend.restype = void_p +objc.objc_msgSend.argtypes = [void_p, void_p] + +msg = objc.objc_msgSend + +def _utf8(s): + """ensure utf8 bytes""" + if not isinstance(s, bytes): + s = s.encode('utf8') + return s + +def n(name): + """create a selector name (for ObjC methods)""" + return objc.sel_registerName(_utf8(name)) + +def C(classname): + """get an ObjC Class by name""" + return objc.objc_getClass(_utf8(classname)) + +# end obj-c boilerplate from appnope + +# CoreFoundation C-API calls we will use: +CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) + +CFFileDescriptorCreate = CoreFoundation.CFFileDescriptorCreate +CFFileDescriptorCreate.restype = void_p +CFFileDescriptorCreate.argtypes = [void_p, ctypes.c_int, ctypes.c_bool, void_p, void_p] + +CFFileDescriptorGetNativeDescriptor = CoreFoundation.CFFileDescriptorGetNativeDescriptor +CFFileDescriptorGetNativeDescriptor.restype = ctypes.c_int +CFFileDescriptorGetNativeDescriptor.argtypes = [void_p] + +CFFileDescriptorEnableCallBacks = CoreFoundation.CFFileDescriptorEnableCallBacks +CFFileDescriptorEnableCallBacks.restype = None +CFFileDescriptorEnableCallBacks.argtypes = [void_p, ctypes.c_ulong] + +CFFileDescriptorCreateRunLoopSource = CoreFoundation.CFFileDescriptorCreateRunLoopSource +CFFileDescriptorCreateRunLoopSource.restype = void_p +CFFileDescriptorCreateRunLoopSource.argtypes = [void_p, void_p, void_p] + +CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent +CFRunLoopGetCurrent.restype = void_p + +CFRunLoopAddSource = CoreFoundation.CFRunLoopAddSource +CFRunLoopAddSource.restype = None +CFRunLoopAddSource.argtypes = [void_p, void_p, void_p] + +CFRelease = CoreFoundation.CFRelease +CFRelease.restype = None +CFRelease.argtypes = [void_p] + +CFFileDescriptorInvalidate = CoreFoundation.CFFileDescriptorInvalidate +CFFileDescriptorInvalidate.restype = None +CFFileDescriptorInvalidate.argtypes = [void_p] + +# From CFFileDescriptor.h +kCFFileDescriptorReadCallBack = 1 +kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes') + + +def _NSApp(): + """Return the global NSApplication instance (NSApp)""" + objc.objc_msgSend.argtypes = [void_p, void_p] + return msg(C('NSApplication'), n('sharedApplication')) + + +def _wake(NSApp): + """Wake the Application""" + objc.objc_msgSend.argtypes = [ + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + ] + event = msg( + C("NSEvent"), + n( + "otherEventWithType:location:modifierFlags:" + "timestamp:windowNumber:context:subtype:data1:data2:" + ), + 15, # Type + 0, # location + 0, # flags + 0, # timestamp + 0, # window + None, # context + 0, # subtype + 0, # data1 + 0, # data2 + ) + objc.objc_msgSend.argtypes = [void_p, void_p, void_p, void_p] + msg(NSApp, n('postEvent:atStart:'), void_p(event), True) + + +_triggered = Event() + +def _input_callback(fdref, flags, info): + """Callback to fire when there's input to be read""" + _triggered.set() + CFFileDescriptorInvalidate(fdref) + CFRelease(fdref) + NSApp = _NSApp() + objc.objc_msgSend.argtypes = [void_p, void_p, void_p] + msg(NSApp, n('stop:'), NSApp) + _wake(NSApp) + +_c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p, void_p) +_c_input_callback = _c_callback_func_type(_input_callback) + + +def _stop_on_read(fd): + """Register callback to stop eventloop when there's data on fd""" + _triggered.clear() + fdref = CFFileDescriptorCreate(None, fd, False, _c_input_callback, None) + CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack) + source = CFFileDescriptorCreateRunLoopSource(None, fdref, 0) + loop = CFRunLoopGetCurrent() + CFRunLoopAddSource(loop, source, kCFRunLoopCommonModes) + CFRelease(source) + + +def inputhook(context): + """Inputhook for Cocoa (NSApp)""" + NSApp = _NSApp() + _stop_on_read(context.fileno()) + objc.objc_msgSend.argtypes = [void_p, void_p] + msg(NSApp, n('run')) + if not _triggered.is_set(): + # app closed without firing callback, + # probably due to last window being closed. + # Run the loop manually in this case, + # since there may be events still to process (#9734) + CoreFoundation.CFRunLoopRun() diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/pyglet.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/pyglet.py index 49ec86d223..f34ed85845 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/pyglet.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/pyglet.py @@ -1,66 +1,66 @@ -"""Enable pyglet to be used interactively with prompt_toolkit -""" - -import sys -import time -from timeit import default_timer as clock -import pyglet - -# On linux only, window.flip() has a bug that causes an AttributeError on -# window close. For details, see: -# http://groups.google.com/group/pyglet-users/browse_thread/thread/47c1aab9aa4a3d23/c22f9e819826799e?#c22f9e819826799e - -if sys.platform.startswith('linux'): - def flip(window): - try: - window.flip() - except AttributeError: - pass -else: - def flip(window): - window.flip() - - -def inputhook(context): - """Run the pyglet event loop by processing pending events only. - - This keeps processing pending events until stdin is ready. After - processing all pending events, a call to time.sleep is inserted. This is - needed, otherwise, CPU usage is at 100%. This sleep time should be tuned - though for best performance. - """ - # We need to protect against a user pressing Control-C when IPython is - # idle and this is running. We trap KeyboardInterrupt and pass. - try: - t = clock() - while not context.input_is_ready(): - pyglet.clock.tick() - for window in pyglet.app.windows: - window.switch_to() - window.dispatch_events() - window.dispatch_event('on_draw') - flip(window) - - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - except KeyboardInterrupt: - pass +"""Enable pyglet to be used interactively with prompt_toolkit +""" + +import sys +import time +from timeit import default_timer as clock +import pyglet + +# On linux only, window.flip() has a bug that causes an AttributeError on +# window close. For details, see: +# http://groups.google.com/group/pyglet-users/browse_thread/thread/47c1aab9aa4a3d23/c22f9e819826799e?#c22f9e819826799e + +if sys.platform.startswith('linux'): + def flip(window): + try: + window.flip() + except AttributeError: + pass +else: + def flip(window): + window.flip() + + +def inputhook(context): + """Run the pyglet event loop by processing pending events only. + + This keeps processing pending events until stdin is ready. After + processing all pending events, a call to time.sleep is inserted. This is + needed, otherwise, CPU usage is at 100%. This sleep time should be tuned + though for best performance. + """ + # We need to protect against a user pressing Control-C when IPython is + # idle and this is running. We trap KeyboardInterrupt and pass. + try: + t = clock() + while not context.input_is_ready(): + pyglet.clock.tick() + for window in pyglet.app.windows: + window.switch_to() + window.dispatch_events() + window.dispatch_event('on_draw') + flip(window) + + # We need to sleep at this point to keep the idle CPU load + # low. However, if sleep to long, GUI response is poor. As + # a compromise, we watch how often GUI events are being processed + # and switch between a short and long sleep time. Here are some + # stats useful in helping to tune this. + # time CPU load + # 0.001 13% + # 0.005 3% + # 0.01 1.5% + # 0.05 0.5% + used_time = clock() - t + if used_time > 10.0: + # print 'Sleep for 1 s' # dbg + time.sleep(1.0) + elif used_time > 0.1: + # Few GUI events coming in, so we can sleep longer + # print 'Sleep for 0.05 s' # dbg + time.sleep(0.05) + else: + # Many GUI events coming in, so sleep only very little + time.sleep(0.001) + except KeyboardInterrupt: + pass diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/qt.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/qt.py index b999f5aa17..dde9238236 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/qt.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/qt.py @@ -1,83 +1,83 @@ -import sys -import os -from IPython.external.qt_for_kernel import QtCore, QtGui, enum_helper -from IPython import get_ipython - -# If we create a QApplication, keep a reference to it so that it doesn't get -# garbage collected. -_appref = None -_already_warned = False - - -def _exec(obj): - # exec on PyQt6, exec_ elsewhere. - obj.exec() if hasattr(obj, "exec") else obj.exec_() - - -def _reclaim_excepthook(): - shell = get_ipython() - if shell is not None: - sys.excepthook = shell.excepthook - - -def inputhook(context): - global _appref - app = QtCore.QCoreApplication.instance() - if not app: - if sys.platform == 'linux': - if not os.environ.get('DISPLAY') \ - and not os.environ.get('WAYLAND_DISPLAY'): - import warnings - global _already_warned - if not _already_warned: - _already_warned = True - warnings.warn( - 'The DISPLAY or WAYLAND_DISPLAY environment variable is ' - 'not set or empty and Qt5 requires this environment ' - 'variable. Deactivate Qt5 code.' - ) - return - try: - QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) - except AttributeError: # Only for Qt>=5.6, <6. - pass - try: - QtCore.QApplication.setHighDpiScaleFactorRoundingPolicy( - QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough - ) - except AttributeError: # Only for Qt>=5.14. - pass - _appref = app = QtGui.QApplication([" "]) - - # "reclaim" IPython sys.excepthook after event loop starts - # without this, it defaults back to BaseIPythonApplication.excepthook - # and exceptions in the Qt event loop are rendered without traceback - # formatting and look like "bug in IPython". - QtCore.QTimer.singleShot(0, _reclaim_excepthook) - - event_loop = QtCore.QEventLoop(app) - - if sys.platform == 'win32': - # The QSocketNotifier method doesn't appear to work on Windows. - # Use polling instead. - timer = QtCore.QTimer() - timer.timeout.connect(event_loop.quit) - while not context.input_is_ready(): - timer.start(50) # 50 ms - _exec(event_loop) - timer.stop() - else: - # On POSIX platforms, we can use a file descriptor to quit the event - # loop when there is input ready to read. - notifier = QtCore.QSocketNotifier( - context.fileno(), enum_helper("QtCore.QSocketNotifier.Type").Read - ) - try: - # connect the callback we care about before we turn it on - notifier.activated.connect(lambda: event_loop.exit()) - notifier.setEnabled(True) - # only start the event loop we are not already flipped - if not context.input_is_ready(): - _exec(event_loop) - finally: - notifier.setEnabled(False) +import sys +import os +from IPython.external.qt_for_kernel import QtCore, QtGui, enum_helper +from IPython import get_ipython + +# If we create a QApplication, keep a reference to it so that it doesn't get +# garbage collected. +_appref = None +_already_warned = False + + +def _exec(obj): + # exec on PyQt6, exec_ elsewhere. + obj.exec() if hasattr(obj, "exec") else obj.exec_() + + +def _reclaim_excepthook(): + shell = get_ipython() + if shell is not None: + sys.excepthook = shell.excepthook + + +def inputhook(context): + global _appref + app = QtCore.QCoreApplication.instance() + if not app: + if sys.platform == 'linux': + if not os.environ.get('DISPLAY') \ + and not os.environ.get('WAYLAND_DISPLAY'): + import warnings + global _already_warned + if not _already_warned: + _already_warned = True + warnings.warn( + 'The DISPLAY or WAYLAND_DISPLAY environment variable is ' + 'not set or empty and Qt5 requires this environment ' + 'variable. Deactivate Qt5 code.' + ) + return + try: + QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) + except AttributeError: # Only for Qt>=5.6, <6. + pass + try: + QtCore.QApplication.setHighDpiScaleFactorRoundingPolicy( + QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough + ) + except AttributeError: # Only for Qt>=5.14. + pass + _appref = app = QtGui.QApplication([" "]) + + # "reclaim" IPython sys.excepthook after event loop starts + # without this, it defaults back to BaseIPythonApplication.excepthook + # and exceptions in the Qt event loop are rendered without traceback + # formatting and look like "bug in IPython". + QtCore.QTimer.singleShot(0, _reclaim_excepthook) + + event_loop = QtCore.QEventLoop(app) + + if sys.platform == 'win32': + # The QSocketNotifier method doesn't appear to work on Windows. + # Use polling instead. + timer = QtCore.QTimer() + timer.timeout.connect(event_loop.quit) + while not context.input_is_ready(): + timer.start(50) # 50 ms + _exec(event_loop) + timer.stop() + else: + # On POSIX platforms, we can use a file descriptor to quit the event + # loop when there is input ready to read. + notifier = QtCore.QSocketNotifier( + context.fileno(), enum_helper("QtCore.QSocketNotifier.Type").Read + ) + try: + # connect the callback we care about before we turn it on + notifier.activated.connect(lambda: event_loop.exit()) + notifier.setEnabled(True) + # only start the event loop we are not already flipped + if not context.input_is_ready(): + _exec(event_loop) + finally: + notifier.setEnabled(False) diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/tk.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/tk.py index 2715505f1f..6afc276747 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/tk.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/tk.py @@ -1,90 +1,90 @@ -# Code borrowed from ptpython -# https://github.com/jonathanslenders/ptpython/blob/86b71a89626114b18898a0af463978bdb32eeb70/ptpython/eventloop.py - -# Copyright (c) 2015, Jonathan Slenders -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, this -# list of conditions and the following disclaimer in the documentation and/or -# other materials provided with the distribution. -# -# * Neither the name of the {organization} nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -""" -Wrapper around the eventloop that gives some time to the Tkinter GUI to process -events when it's loaded and while we are waiting for input at the REPL. This -way we don't block the UI of for instance ``turtle`` and other Tk libraries. - -(Normally Tkinter registers it's callbacks in ``PyOS_InputHook`` to integrate -in readline. ``prompt-toolkit`` doesn't understand that input hook, but this -will fix it for Tk.) -""" -import time - -import _tkinter -import tkinter - -def inputhook(inputhook_context): - """ - Inputhook for Tk. - Run the Tk eventloop until prompt-toolkit needs to process the next input. - """ - # Get the current TK application. - root = tkinter._default_root - - def wait_using_filehandler(): - """ - Run the TK eventloop until the file handler that we got from the - inputhook becomes readable. - """ - # Add a handler that sets the stop flag when `prompt-toolkit` has input - # to process. - stop = [False] - def done(*a): - stop[0] = True - - root.createfilehandler(inputhook_context.fileno(), _tkinter.READABLE, done) - - # Run the TK event loop as long as we don't receive input. - while root.dooneevent(_tkinter.ALL_EVENTS): - if stop[0]: - break - - root.deletefilehandler(inputhook_context.fileno()) - - def wait_using_polling(): - """ - Windows TK doesn't support 'createfilehandler'. - So, run the TK eventloop and poll until input is ready. - """ - while not inputhook_context.input_is_ready(): - while root.dooneevent(_tkinter.ALL_EVENTS | _tkinter.DONT_WAIT): - pass - # Sleep to make the CPU idle, but not too long, so that the UI - # stays responsive. - time.sleep(.01) - - if root is not None: - if hasattr(root, 'createfilehandler'): - wait_using_filehandler() - else: - wait_using_polling() +# Code borrowed from ptpython +# https://github.com/jonathanslenders/ptpython/blob/86b71a89626114b18898a0af463978bdb32eeb70/ptpython/eventloop.py + +# Copyright (c) 2015, Jonathan Slenders +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# +# * Neither the name of the {organization} nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +""" +Wrapper around the eventloop that gives some time to the Tkinter GUI to process +events when it's loaded and while we are waiting for input at the REPL. This +way we don't block the UI of for instance ``turtle`` and other Tk libraries. + +(Normally Tkinter registers it's callbacks in ``PyOS_InputHook`` to integrate +in readline. ``prompt-toolkit`` doesn't understand that input hook, but this +will fix it for Tk.) +""" +import time + +import _tkinter +import tkinter + +def inputhook(inputhook_context): + """ + Inputhook for Tk. + Run the Tk eventloop until prompt-toolkit needs to process the next input. + """ + # Get the current TK application. + root = tkinter._default_root + + def wait_using_filehandler(): + """ + Run the TK eventloop until the file handler that we got from the + inputhook becomes readable. + """ + # Add a handler that sets the stop flag when `prompt-toolkit` has input + # to process. + stop = [False] + def done(*a): + stop[0] = True + + root.createfilehandler(inputhook_context.fileno(), _tkinter.READABLE, done) + + # Run the TK event loop as long as we don't receive input. + while root.dooneevent(_tkinter.ALL_EVENTS): + if stop[0]: + break + + root.deletefilehandler(inputhook_context.fileno()) + + def wait_using_polling(): + """ + Windows TK doesn't support 'createfilehandler'. + So, run the TK eventloop and poll until input is ready. + """ + while not inputhook_context.input_is_ready(): + while root.dooneevent(_tkinter.ALL_EVENTS | _tkinter.DONT_WAIT): + pass + # Sleep to make the CPU idle, but not too long, so that the UI + # stays responsive. + time.sleep(.01) + + if root is not None: + if hasattr(root, 'createfilehandler'): + wait_using_filehandler() + else: + wait_using_polling() diff --git a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/wx.py b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/wx.py index a0f4442c77..3b7b0e0a33 100644 --- a/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/wx.py +++ b/contrib/python/ipython/py3/IPython/terminal/pt_inputhooks/wx.py @@ -1,219 +1,219 @@ -"""Enable wxPython to be used interactively in prompt_toolkit -""" - -import sys -import signal -import time -from timeit import default_timer as clock -import wx - - -def ignore_keyboardinterrupts(func): - """Decorator which causes KeyboardInterrupt exceptions to be ignored during - execution of the decorated function. - - This is used by the inputhook functions to handle the event where the user - presses CTRL+C while IPython is idle, and the inputhook loop is running. In - this case, we want to ignore interrupts. - """ - def wrapper(*args, **kwargs): - try: - func(*args, **kwargs) - except KeyboardInterrupt: - pass - return wrapper - - -@ignore_keyboardinterrupts -def inputhook_wx1(context): - """Run the wx event loop by processing pending events only. - - This approach seems to work, but its performance is not great as it - relies on having PyOS_InputHook called regularly. - """ - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - - # Make a temporary event loop and process system events until - # there are no more waiting, then allow idle events (which - # will also deal with pending or posted wx events.) - evtloop = wx.EventLoop() - ea = wx.EventLoopActivator(evtloop) - while evtloop.Pending(): - evtloop.Dispatch() - app.ProcessIdle() - del ea - return 0 - - -class EventLoopTimer(wx.Timer): - - def __init__(self, func): - self.func = func - wx.Timer.__init__(self) - - def Notify(self): - self.func() - - -class EventLoopRunner(object): - - def Run(self, time, input_is_ready): - self.input_is_ready = input_is_ready - self.evtloop = wx.EventLoop() - self.timer = EventLoopTimer(self.check_stdin) - self.timer.Start(time) - self.evtloop.Run() - - def check_stdin(self): - if self.input_is_ready(): - self.timer.Stop() - self.evtloop.Exit() - - -@ignore_keyboardinterrupts -def inputhook_wx2(context): - """Run the wx event loop, polling for stdin. - - This version runs the wx eventloop for an undetermined amount of time, - during which it periodically checks to see if anything is ready on - stdin. If anything is ready on stdin, the event loop exits. - - The argument to elr.Run controls how often the event loop looks at stdin. - This determines the responsiveness at the keyboard. A setting of 1000 - enables a user to type at most 1 char per second. I have found that a - setting of 10 gives good keyboard response. We can shorten it further, - but eventually performance would suffer from calling select/kbhit too - often. - """ - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - elr = EventLoopRunner() - # As this time is made shorter, keyboard response improves, but idle - # CPU load goes up. 10 ms seems like a good compromise. - elr.Run(time=10, # CHANGE time here to control polling interval - input_is_ready=context.input_is_ready) - return 0 - - -@ignore_keyboardinterrupts -def inputhook_wx3(context): - """Run the wx event loop by processing pending events only. - - This is like inputhook_wx1, but it keeps processing pending events - until stdin is ready. After processing all pending events, a call to - time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%. - This sleep time should be tuned though for best performance. - """ - app = wx.GetApp() - if app is not None: - assert wx.Thread_IsMain() - - # The import of wx on Linux sets the handler for signal.SIGINT - # to 0. This is a bug in wx or gtk. We fix by just setting it - # back to the Python default. - if not callable(signal.getsignal(signal.SIGINT)): - signal.signal(signal.SIGINT, signal.default_int_handler) - - evtloop = wx.EventLoop() - ea = wx.EventLoopActivator(evtloop) - t = clock() - while not context.input_is_ready(): - while evtloop.Pending(): - t = clock() - evtloop.Dispatch() - app.ProcessIdle() - # We need to sleep at this point to keep the idle CPU load - # low. However, if sleep to long, GUI response is poor. As - # a compromise, we watch how often GUI events are being processed - # and switch between a short and long sleep time. Here are some - # stats useful in helping to tune this. - # time CPU load - # 0.001 13% - # 0.005 3% - # 0.01 1.5% - # 0.05 0.5% - used_time = clock() - t - if used_time > 10.0: - # print 'Sleep for 1 s' # dbg - time.sleep(1.0) - elif used_time > 0.1: - # Few GUI events coming in, so we can sleep longer - # print 'Sleep for 0.05 s' # dbg - time.sleep(0.05) - else: - # Many GUI events coming in, so sleep only very little - time.sleep(0.001) - del ea - return 0 - - -@ignore_keyboardinterrupts -def inputhook_wxphoenix(context): - """Run the wx event loop until the user provides more input. - - This input hook is suitable for use with wxPython >= 4 (a.k.a. Phoenix). - - It uses the same approach to that used in - ipykernel.eventloops.loop_wx. The wx.MainLoop is executed, and a wx.Timer - is used to periodically poll the context for input. As soon as input is - ready, the wx.MainLoop is stopped. - """ - - app = wx.GetApp() - - if app is None: - return - - if context.input_is_ready(): - return - - assert wx.IsMainThread() - - # Wx uses milliseconds - poll_interval = 100 - - # Use a wx.Timer to periodically check whether input is ready - as soon as - # it is, we exit the main loop - timer = wx.Timer() - - def poll(ev): - if context.input_is_ready(): - timer.Stop() - app.ExitMainLoop() - - timer.Start(poll_interval) - timer.Bind(wx.EVT_TIMER, poll) - - # The import of wx on Linux sets the handler for signal.SIGINT to 0. This - # is a bug in wx or gtk. We fix by just setting it back to the Python - # default. - if not callable(signal.getsignal(signal.SIGINT)): - signal.signal(signal.SIGINT, signal.default_int_handler) - - # The SetExitOnFrameDelete call allows us to run the wx mainloop without - # having a frame open. - app.SetExitOnFrameDelete(False) - app.MainLoop() - - -# Get the major wx version number to figure out what input hook we should use. -major_version = 3 - -try: - major_version = int(wx.__version__[0]) -except Exception: - pass - -# Use the phoenix hook on all platforms for wxpython >= 4 -if major_version >= 4: - inputhook = inputhook_wxphoenix -# On OSX, evtloop.Pending() always returns True, regardless of there being -# any events pending. As such we can't use implementations 1 or 3 of the -# inputhook as those depend on a pending/dispatch loop. -elif sys.platform == 'darwin': - inputhook = inputhook_wx2 -else: - inputhook = inputhook_wx3 +"""Enable wxPython to be used interactively in prompt_toolkit +""" + +import sys +import signal +import time +from timeit import default_timer as clock +import wx + + +def ignore_keyboardinterrupts(func): + """Decorator which causes KeyboardInterrupt exceptions to be ignored during + execution of the decorated function. + + This is used by the inputhook functions to handle the event where the user + presses CTRL+C while IPython is idle, and the inputhook loop is running. In + this case, we want to ignore interrupts. + """ + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + except KeyboardInterrupt: + pass + return wrapper + + +@ignore_keyboardinterrupts +def inputhook_wx1(context): + """Run the wx event loop by processing pending events only. + + This approach seems to work, but its performance is not great as it + relies on having PyOS_InputHook called regularly. + """ + app = wx.GetApp() + if app is not None: + assert wx.Thread_IsMain() + + # Make a temporary event loop and process system events until + # there are no more waiting, then allow idle events (which + # will also deal with pending or posted wx events.) + evtloop = wx.EventLoop() + ea = wx.EventLoopActivator(evtloop) + while evtloop.Pending(): + evtloop.Dispatch() + app.ProcessIdle() + del ea + return 0 + + +class EventLoopTimer(wx.Timer): + + def __init__(self, func): + self.func = func + wx.Timer.__init__(self) + + def Notify(self): + self.func() + + +class EventLoopRunner(object): + + def Run(self, time, input_is_ready): + self.input_is_ready = input_is_ready + self.evtloop = wx.EventLoop() + self.timer = EventLoopTimer(self.check_stdin) + self.timer.Start(time) + self.evtloop.Run() + + def check_stdin(self): + if self.input_is_ready(): + self.timer.Stop() + self.evtloop.Exit() + + +@ignore_keyboardinterrupts +def inputhook_wx2(context): + """Run the wx event loop, polling for stdin. + + This version runs the wx eventloop for an undetermined amount of time, + during which it periodically checks to see if anything is ready on + stdin. If anything is ready on stdin, the event loop exits. + + The argument to elr.Run controls how often the event loop looks at stdin. + This determines the responsiveness at the keyboard. A setting of 1000 + enables a user to type at most 1 char per second. I have found that a + setting of 10 gives good keyboard response. We can shorten it further, + but eventually performance would suffer from calling select/kbhit too + often. + """ + app = wx.GetApp() + if app is not None: + assert wx.Thread_IsMain() + elr = EventLoopRunner() + # As this time is made shorter, keyboard response improves, but idle + # CPU load goes up. 10 ms seems like a good compromise. + elr.Run(time=10, # CHANGE time here to control polling interval + input_is_ready=context.input_is_ready) + return 0 + + +@ignore_keyboardinterrupts +def inputhook_wx3(context): + """Run the wx event loop by processing pending events only. + + This is like inputhook_wx1, but it keeps processing pending events + until stdin is ready. After processing all pending events, a call to + time.sleep is inserted. This is needed, otherwise, CPU usage is at 100%. + This sleep time should be tuned though for best performance. + """ + app = wx.GetApp() + if app is not None: + assert wx.Thread_IsMain() + + # The import of wx on Linux sets the handler for signal.SIGINT + # to 0. This is a bug in wx or gtk. We fix by just setting it + # back to the Python default. + if not callable(signal.getsignal(signal.SIGINT)): + signal.signal(signal.SIGINT, signal.default_int_handler) + + evtloop = wx.EventLoop() + ea = wx.EventLoopActivator(evtloop) + t = clock() + while not context.input_is_ready(): + while evtloop.Pending(): + t = clock() + evtloop.Dispatch() + app.ProcessIdle() + # We need to sleep at this point to keep the idle CPU load + # low. However, if sleep to long, GUI response is poor. As + # a compromise, we watch how often GUI events are being processed + # and switch between a short and long sleep time. Here are some + # stats useful in helping to tune this. + # time CPU load + # 0.001 13% + # 0.005 3% + # 0.01 1.5% + # 0.05 0.5% + used_time = clock() - t + if used_time > 10.0: + # print 'Sleep for 1 s' # dbg + time.sleep(1.0) + elif used_time > 0.1: + # Few GUI events coming in, so we can sleep longer + # print 'Sleep for 0.05 s' # dbg + time.sleep(0.05) + else: + # Many GUI events coming in, so sleep only very little + time.sleep(0.001) + del ea + return 0 + + +@ignore_keyboardinterrupts +def inputhook_wxphoenix(context): + """Run the wx event loop until the user provides more input. + + This input hook is suitable for use with wxPython >= 4 (a.k.a. Phoenix). + + It uses the same approach to that used in + ipykernel.eventloops.loop_wx. The wx.MainLoop is executed, and a wx.Timer + is used to periodically poll the context for input. As soon as input is + ready, the wx.MainLoop is stopped. + """ + + app = wx.GetApp() + + if app is None: + return + + if context.input_is_ready(): + return + + assert wx.IsMainThread() + + # Wx uses milliseconds + poll_interval = 100 + + # Use a wx.Timer to periodically check whether input is ready - as soon as + # it is, we exit the main loop + timer = wx.Timer() + + def poll(ev): + if context.input_is_ready(): + timer.Stop() + app.ExitMainLoop() + + timer.Start(poll_interval) + timer.Bind(wx.EVT_TIMER, poll) + + # The import of wx on Linux sets the handler for signal.SIGINT to 0. This + # is a bug in wx or gtk. We fix by just setting it back to the Python + # default. + if not callable(signal.getsignal(signal.SIGINT)): + signal.signal(signal.SIGINT, signal.default_int_handler) + + # The SetExitOnFrameDelete call allows us to run the wx mainloop without + # having a frame open. + app.SetExitOnFrameDelete(False) + app.MainLoop() + + +# Get the major wx version number to figure out what input hook we should use. +major_version = 3 + +try: + major_version = int(wx.__version__[0]) +except Exception: + pass + +# Use the phoenix hook on all platforms for wxpython >= 4 +if major_version >= 4: + inputhook = inputhook_wxphoenix +# On OSX, evtloop.Pending() always returns True, regardless of there being +# any events pending. As such we can't use implementations 1 or 3 of the +# inputhook as those depend on a pending/dispatch loop. +elif sys.platform == 'darwin': + inputhook = inputhook_wx2 +else: + inputhook = inputhook_wx3 diff --git a/contrib/python/ipython/py3/IPython/terminal/ptshell.py b/contrib/python/ipython/py3/IPython/terminal/ptshell.py index 666d3c5b51..aad111b041 100644 --- a/contrib/python/ipython/py3/IPython/terminal/ptshell.py +++ b/contrib/python/ipython/py3/IPython/terminal/ptshell.py @@ -1,8 +1,8 @@ -raise DeprecationWarning("""DEPRECATED: - -After Popular request and decision from the BDFL: -`IPython.terminal.ptshell` has been moved back to `IPython.terminal.interactiveshell` -during the beta cycle (after IPython 5.0.beta3) Sorry about that. - -This file will be removed in 5.0 rc or final. -""") +raise DeprecationWarning("""DEPRECATED: + +After Popular request and decision from the BDFL: +`IPython.terminal.ptshell` has been moved back to `IPython.terminal.interactiveshell` +during the beta cycle (after IPython 5.0.beta3) Sorry about that. + +This file will be removed in 5.0 rc or final. +""") diff --git a/contrib/python/ipython/py3/IPython/terminal/ptutils.py b/contrib/python/ipython/py3/IPython/terminal/ptutils.py index 3e5d3c5c77..380087dc29 100644 --- a/contrib/python/ipython/py3/IPython/terminal/ptutils.py +++ b/contrib/python/ipython/py3/IPython/terminal/ptutils.py @@ -1,197 +1,197 @@ -"""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 -import sys -import traceback - -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 - -_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][0], 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][0], 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) +"""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 +import sys +import traceback + +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 + +_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][0], 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][0], 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) diff --git a/contrib/python/ipython/py3/IPython/terminal/shortcuts.py b/contrib/python/ipython/py3/IPython/terminal/shortcuts.py index a23fa091a0..6f4468b3a5 100644 --- a/contrib/python/ipython/py3/IPython/terminal/shortcuts.py +++ b/contrib/python/ipython/py3/IPython/terminal/shortcuts.py @@ -1,276 +1,276 @@ -""" -Module to define and register Terminal IPython shortcuts with -:mod:`prompt_toolkit` -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import warnings -import signal -import sys -from typing import Callable - - -from prompt_toolkit.application.current import get_app -from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER -from prompt_toolkit.filters import (has_focus, has_selection, Condition, - vi_insert_mode, emacs_insert_mode, has_completions, vi_mode) -from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline -from prompt_toolkit.key_binding import KeyBindings - -from IPython.utils.decorators import undoc - -@undoc -@Condition -def cursor_in_leading_ws(): - before = get_app().current_buffer.document.current_line_before_cursor - return (not before) or before.isspace() - - -def create_ipython_shortcuts(shell): - """Set up the prompt_toolkit keyboard shortcuts for IPython""" - - kb = KeyBindings() - insert_mode = vi_insert_mode | emacs_insert_mode - - if getattr(shell, 'handle_return', None): - return_handler = shell.handle_return(shell) - else: - return_handler = newline_or_execute_outer(shell) - - kb.add('enter', filter=(has_focus(DEFAULT_BUFFER) - & ~has_selection - & insert_mode - ))(return_handler) - - def reformat_and_execute(event): - reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell) - event.current_buffer.validate_and_handle() - - kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER) - & ~has_selection - & insert_mode - ))(reformat_and_execute) - - kb.add('c-\\')(force_exit) - - kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) - )(previous_history_or_previous_completion) - - kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) - )(next_history_or_next_completion) - - kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions) - )(dismiss_completion) - - kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer) - - kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer) - - supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP')) - kb.add('c-z', filter=supports_suspend)(suspend_to_bg) - - # Ctrl+I == Tab - kb.add('tab', filter=(has_focus(DEFAULT_BUFFER) - & ~has_selection - & insert_mode - & cursor_in_leading_ws - ))(indent_buffer) - kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode) - )(newline_autoindent_outer(shell.input_transformer_manager)) - - kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor) - - if shell.display_completions == 'readlinelike': - kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER) - & ~has_selection - & insert_mode - & ~cursor_in_leading_ws - ))(display_completions_like_readline) - - if sys.platform == 'win32': - kb.add('c-v', filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste) - - return kb - - -def reformat_text_before_cursor(buffer, document, shell): - text = buffer.delete_before_cursor(len(document.text[:document.cursor_position])) - try: - formatted_text = shell.reformat_handler(text) - buffer.insert_text(formatted_text) - except Exception as e: - buffer.insert_text(text) - - -def newline_or_execute_outer(shell): - - def newline_or_execute(event): - """When the user presses return, insert a newline or execute the code.""" - b = event.current_buffer - d = b.document - - if b.complete_state: - cc = b.complete_state.current_completion - if cc: - b.apply_completion(cc) - else: - b.cancel_completion() - return - - # If there's only one line, treat it as if the cursor is at the end. - # See https://github.com/ipython/ipython/issues/10425 - if d.line_count == 1: - check_text = d.text - else: - check_text = d.text[:d.cursor_position] - status, indent = shell.check_complete(check_text) - - # if all we have after the cursor is whitespace: reformat current text - # before cursor - after_cursor = d.text[d.cursor_position:] - reformatted = False - if not after_cursor.strip(): - reformat_text_before_cursor(b, d, shell) - reformatted = True - if not (d.on_last_line or - d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() - ): - if shell.autoindent: - b.insert_text('\n' + indent) - else: - b.insert_text('\n') - return - - if (status != 'incomplete') and b.accept_handler: - if not reformatted: - reformat_text_before_cursor(b, d, shell) - b.validate_and_handle() - else: - if shell.autoindent: - b.insert_text('\n' + indent) - else: - b.insert_text('\n') - return newline_or_execute - - -def previous_history_or_previous_completion(event): - """ - Control-P in vi edit mode on readline is history next, unlike default prompt toolkit. - - If completer is open this still select previous completion. - """ - event.current_buffer.auto_up() - - -def next_history_or_next_completion(event): - """ - Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit. - - If completer is open this still select next completion. - """ - event.current_buffer.auto_down() - - -def dismiss_completion(event): - b = event.current_buffer - if b.complete_state: - b.cancel_completion() - - -def reset_buffer(event): - b = event.current_buffer - if b.complete_state: - b.cancel_completion() - else: - b.reset() - - -def reset_search_buffer(event): - if event.current_buffer.document.text: - event.current_buffer.reset() - else: - event.app.layout.focus(DEFAULT_BUFFER) - -def suspend_to_bg(event): - event.app.suspend_to_background() - -def force_exit(event): - """ - Force exit (with a non-zero return value) - """ - sys.exit("Quit") - -def indent_buffer(event): - event.current_buffer.insert_text(' ' * 4) - -@undoc -def newline_with_copy_margin(event): - """ - DEPRECATED since IPython 6.0 - - See :any:`newline_autoindent_outer` for a replacement. - - Preserve margin and cursor position when using - Control-O to insert a newline in EMACS mode - """ - warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. " - "see `newline_autoindent_outer(shell)(event)` for a replacement.", - DeprecationWarning, stacklevel=2) - - b = event.current_buffer - cursor_start_pos = b.document.cursor_position_col - b.newline(copy_margin=True) - b.cursor_up(count=1) - cursor_end_pos = b.document.cursor_position_col - if cursor_start_pos != cursor_end_pos: - pos_diff = cursor_start_pos - cursor_end_pos - b.cursor_right(count=pos_diff) - -def newline_autoindent_outer(inputsplitter) -> Callable[..., None]: - """ - Return a function suitable for inserting a indented newline after the cursor. - - Fancier version of deprecated ``newline_with_copy_margin`` which should - compute the correct indentation of the inserted line. That is to say, indent - by 4 extra space after a function definition, class definition, context - manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``. - """ - - def newline_autoindent(event): - """insert a newline after the cursor indented appropriately.""" - b = event.current_buffer - d = b.document - - if b.complete_state: - b.cancel_completion() - text = d.text[:d.cursor_position] + '\n' - _, indent = inputsplitter.check_complete(text) - b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False) - - return newline_autoindent - - -def open_input_in_editor(event): - event.app.current_buffer.open_in_editor() - - -if sys.platform == 'win32': - from IPython.core.error import TryNext - from IPython.lib.clipboard import (ClipboardEmpty, - win32_clipboard_get, - tkinter_clipboard_get) - - @undoc - def win_paste(event): - try: - text = win32_clipboard_get() - except TryNext: - try: - text = tkinter_clipboard_get() - except (TryNext, ClipboardEmpty): - return - except ClipboardEmpty: - return - event.current_buffer.insert_text(text.replace('\t', ' ' * 4)) +""" +Module to define and register Terminal IPython shortcuts with +:mod:`prompt_toolkit` +""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import warnings +import signal +import sys +from typing import Callable + + +from prompt_toolkit.application.current import get_app +from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER +from prompt_toolkit.filters import (has_focus, has_selection, Condition, + vi_insert_mode, emacs_insert_mode, has_completions, vi_mode) +from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline +from prompt_toolkit.key_binding import KeyBindings + +from IPython.utils.decorators import undoc + +@undoc +@Condition +def cursor_in_leading_ws(): + before = get_app().current_buffer.document.current_line_before_cursor + return (not before) or before.isspace() + + +def create_ipython_shortcuts(shell): + """Set up the prompt_toolkit keyboard shortcuts for IPython""" + + kb = KeyBindings() + insert_mode = vi_insert_mode | emacs_insert_mode + + if getattr(shell, 'handle_return', None): + return_handler = shell.handle_return(shell) + else: + return_handler = newline_or_execute_outer(shell) + + kb.add('enter', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode + ))(return_handler) + + def reformat_and_execute(event): + reformat_text_before_cursor(event.current_buffer, event.current_buffer.document, shell) + event.current_buffer.validate_and_handle() + + kb.add('escape', 'enter', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode + ))(reformat_and_execute) + + kb.add('c-\\')(force_exit) + + kb.add('c-p', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) + )(previous_history_or_previous_completion) + + kb.add('c-n', filter=(vi_insert_mode & has_focus(DEFAULT_BUFFER)) + )(next_history_or_next_completion) + + kb.add('c-g', filter=(has_focus(DEFAULT_BUFFER) & has_completions) + )(dismiss_completion) + + kb.add('c-c', filter=has_focus(DEFAULT_BUFFER))(reset_buffer) + + kb.add('c-c', filter=has_focus(SEARCH_BUFFER))(reset_search_buffer) + + supports_suspend = Condition(lambda: hasattr(signal, 'SIGTSTP')) + kb.add('c-z', filter=supports_suspend)(suspend_to_bg) + + # Ctrl+I == Tab + kb.add('tab', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode + & cursor_in_leading_ws + ))(indent_buffer) + kb.add('c-o', filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode) + )(newline_autoindent_outer(shell.input_transformer_manager)) + + kb.add('f2', filter=has_focus(DEFAULT_BUFFER))(open_input_in_editor) + + if shell.display_completions == 'readlinelike': + kb.add('c-i', filter=(has_focus(DEFAULT_BUFFER) + & ~has_selection + & insert_mode + & ~cursor_in_leading_ws + ))(display_completions_like_readline) + + if sys.platform == 'win32': + kb.add('c-v', filter=(has_focus(DEFAULT_BUFFER) & ~vi_mode))(win_paste) + + return kb + + +def reformat_text_before_cursor(buffer, document, shell): + text = buffer.delete_before_cursor(len(document.text[:document.cursor_position])) + try: + formatted_text = shell.reformat_handler(text) + buffer.insert_text(formatted_text) + except Exception as e: + buffer.insert_text(text) + + +def newline_or_execute_outer(shell): + + def newline_or_execute(event): + """When the user presses return, insert a newline or execute the code.""" + b = event.current_buffer + d = b.document + + if b.complete_state: + cc = b.complete_state.current_completion + if cc: + b.apply_completion(cc) + else: + b.cancel_completion() + return + + # If there's only one line, treat it as if the cursor is at the end. + # See https://github.com/ipython/ipython/issues/10425 + if d.line_count == 1: + check_text = d.text + else: + check_text = d.text[:d.cursor_position] + status, indent = shell.check_complete(check_text) + + # if all we have after the cursor is whitespace: reformat current text + # before cursor + after_cursor = d.text[d.cursor_position:] + reformatted = False + if not after_cursor.strip(): + reformat_text_before_cursor(b, d, shell) + reformatted = True + if not (d.on_last_line or + d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end() + ): + if shell.autoindent: + b.insert_text('\n' + indent) + else: + b.insert_text('\n') + return + + if (status != 'incomplete') and b.accept_handler: + if not reformatted: + reformat_text_before_cursor(b, d, shell) + b.validate_and_handle() + else: + if shell.autoindent: + b.insert_text('\n' + indent) + else: + b.insert_text('\n') + return newline_or_execute + + +def previous_history_or_previous_completion(event): + """ + Control-P in vi edit mode on readline is history next, unlike default prompt toolkit. + + If completer is open this still select previous completion. + """ + event.current_buffer.auto_up() + + +def next_history_or_next_completion(event): + """ + Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit. + + If completer is open this still select next completion. + """ + event.current_buffer.auto_down() + + +def dismiss_completion(event): + b = event.current_buffer + if b.complete_state: + b.cancel_completion() + + +def reset_buffer(event): + b = event.current_buffer + if b.complete_state: + b.cancel_completion() + else: + b.reset() + + +def reset_search_buffer(event): + if event.current_buffer.document.text: + event.current_buffer.reset() + else: + event.app.layout.focus(DEFAULT_BUFFER) + +def suspend_to_bg(event): + event.app.suspend_to_background() + +def force_exit(event): + """ + Force exit (with a non-zero return value) + """ + sys.exit("Quit") + +def indent_buffer(event): + event.current_buffer.insert_text(' ' * 4) + +@undoc +def newline_with_copy_margin(event): + """ + DEPRECATED since IPython 6.0 + + See :any:`newline_autoindent_outer` for a replacement. + + Preserve margin and cursor position when using + Control-O to insert a newline in EMACS mode + """ + warnings.warn("`newline_with_copy_margin(event)` is deprecated since IPython 6.0. " + "see `newline_autoindent_outer(shell)(event)` for a replacement.", + DeprecationWarning, stacklevel=2) + + b = event.current_buffer + cursor_start_pos = b.document.cursor_position_col + b.newline(copy_margin=True) + b.cursor_up(count=1) + cursor_end_pos = b.document.cursor_position_col + if cursor_start_pos != cursor_end_pos: + pos_diff = cursor_start_pos - cursor_end_pos + b.cursor_right(count=pos_diff) + +def newline_autoindent_outer(inputsplitter) -> Callable[..., None]: + """ + Return a function suitable for inserting a indented newline after the cursor. + + Fancier version of deprecated ``newline_with_copy_margin`` which should + compute the correct indentation of the inserted line. That is to say, indent + by 4 extra space after a function definition, class definition, context + manager... And dedent by 4 space after ``pass``, ``return``, ``raise ...``. + """ + + def newline_autoindent(event): + """insert a newline after the cursor indented appropriately.""" + b = event.current_buffer + d = b.document + + if b.complete_state: + b.cancel_completion() + text = d.text[:d.cursor_position] + '\n' + _, indent = inputsplitter.check_complete(text) + b.insert_text('\n' + (' ' * (indent or 0)), move_cursor=False) + + return newline_autoindent + + +def open_input_in_editor(event): + event.app.current_buffer.open_in_editor() + + +if sys.platform == 'win32': + from IPython.core.error import TryNext + from IPython.lib.clipboard import (ClipboardEmpty, + win32_clipboard_get, + tkinter_clipboard_get) + + @undoc + def win_paste(event): + try: + text = win32_clipboard_get() + except TryNext: + try: + text = tkinter_clipboard_get() + except (TryNext, ClipboardEmpty): + return + except ClipboardEmpty: + return + event.current_buffer.insert_text(text.replace('\t', ' ' * 4)) |