在ipython 5.0+中按Ctrl-C时在屏幕上留下不完整的行

Sha*_*ger 2 python keyboard-shortcuts ipython keyboardinterrupt prompt-toolkit

在较旧的(我认为是5.0之前的版本)IPython中,如果我正在处理行/块,突然发现我需要研究其他事情来完成它,我的方法是按Ctrl-C,这留下了不完整的行/ block在屏幕上,但未执行,并给了我新的提示。也就是说,我会看到类似以下内容的内容:

In [1]: def foo():
   ...:     stuff^C  # <-- Just realized I needed to check something on stuff usage

In [2]:    # <-- cursor goes to new line, but old stuff still on terminal
Run Code Online (Sandbox Code Playgroud)

在较新的IPython中(这似乎已经从切换readlineprompt_toolkit的“CLI支持框架”),Ctrl-C键的不同的行为; 现在,它没有给我换行符,而是重置了当前行,丢弃了我键入的所有内容,并将光标返回到该行的开头。

# Before:
In [1]: def foo():
   ...:     stuff

# After Ctrl-C:
In [1]:   # Hey, where'd everything go?
Run Code Online (Sandbox Code Playgroud)

这非常烦人,因为在完成任何辅助任务后,我再也无法看到或复制/粘贴正在处理的代码来恢复工作,因此需要重新提示。

我的问题是:有什么方法可以还原旧的IPython行为,其中Ctrl-C执行以下操作:

  1. 至今未执行键入的行/块
  2. 留在屏幕上
  3. 能够选择(在配置时就可以)是否添加到历史记录中(这是个人喜好;您要在历史记录中使用半形式的内容,还是仅在终端上进行复制/粘贴?)
  4. 在到目前为止输入的文字下方为我提供了新的提示

我到处搜索,发现最多的是一个错误报告注释,该注释提到此新行为是“ ...是对IPython早期版本的更改,但这是有意的。”

在IPython或prompt_toolkit文档中,我找不到任何有关修改行为的文档;我已经找到了许多安装了这些处理程序的位置,但是尝试用猴子补丁来更改当前行为却失败了(坦率地说,用猴子补丁来记录未记录的代码意味着我冒着破坏每次升级的风险,所以我想找到一些对此的半支持修复;否则,可以接受hacky的猴子修补程序)。

Sha*_*ger 5

经过更多的研究,我发现了一种受支持的方法,它依赖于IPython键盘快捷键文档(针对5.x6.x的文档略有不同)。

解决方案是使用创建一个文件~/.ipython/profile_default/startup(任何名称,以.py或结尾ipy,例如fixctrlc.py),然后添加以下内容:

from IPython import get_ipython
from prompt_toolkit.enums import DEFAULT_BUFFER
from prompt_toolkit.keys import Keys
from prompt_toolkit.filters import HasFocus, ViInsertMode, EmacsInsertMode

def on_ctrlc(event):
    # Move cursor to end of line and redraw (so whole block is preserved)
    event.cli.current_buffer.cursor_position = len(event.cli.current_buffer.text)
    event.cli._redraw(**redraw_args)

    # (Optional) Put non-empty partial commands in history
    if event.cli.current_buffer.text.strip():
        event.cli.current_buffer.append_to_history()

    if doprint:
        print()                       # Skip to next line past cursor
    event.cli.reset()                 # Reset/redraw prompt
    event.cli.current_buffer.reset()  # Clear buffer so new line is fresh (empty)

ip = get_ipython()
try:
    try:
        # IPython 5-6; render_as_done doesn't exist, but manual print works
        registry = ip.pt_cli.application.key_bindings_registry
        redraw_args = {}
        doprint = True
    except AttributeError:
        # IPython 7+; render_as_done necessary, and removes need for print
        registry = ip.pt_app.key_bindings
        redraw_args = {'render_as_done': True}
        doprint = False
except AttributeError:
    pass  # Old IPython doesn't need special handler
else:
    registry.add_binding(Keys.ControlC,
        filter=(HasFocus(DEFAULT_BUFFER) & (ViInsertMode() | EmacsInsertMode()))
        )(on_ctrlc)
Run Code Online (Sandbox Code Playgroud)

如果您找到更好的解决方案,请随时做出贡献。