【问题标题】:Edit IPython cell in an external editor在外部编辑器中编辑 IPython 单元格
【发布时间】:2015-04-03 06:28:21
【问题描述】:

如果在 IPython 笔记本中有一个键盘快捷键会很棒,它允许在外部编辑器(例如 gvim)中编辑当前单元格的内容。也许只是将当前单元格的内容复制到一个临时文件中,在其上启动 gvim,并在每次保存文件时更新当前单元格(并在退出 gvim 时删除临时文件)。另外,如果从浏览器编辑单元格,可能会更新临时文件,以便 gvim 知道文件已更改。

我知道 vim-ipython 和 ipython-vimception 之类的项目,但它们不符合我的需求。我认为浏览器对于简单的事情已经足够了,但是当需要更强大的编辑时,不需要重新发明轮子。

你知道 IPython notebook 中是否已经存在这样的功能?

谢谢。

【问题讨论】:

    标签: vim editor cell ipython-notebook


    【解决方案1】:

    这就是我想出的。我添加了 2 个快捷方式:

    • 'g' 使用当前单元格的内容启动 gvim(您可以将 gvim 替换为您喜欢的任何文本编辑器)。
    • 'u' 用 gvim 保存的内容更新当前单元格的内容。

    因此,当您想使用首选编辑器编辑单元格时,请点击“g”,对单元格进行所需的更改,将文件保存在编辑器中(并退出),然后点击“u”。

    只需执行此单元格即可启用这些功能:

    %%javascript
    
    IPython.keyboard_manager.command_shortcuts.add_shortcut('g', {
        handler : function (event) {
            var input = IPython.notebook.get_selected_cell().get_text();
            var cmd = "f = open('.toto.py', 'w');f.close()";
            if (input != "") {
                cmd = '%%writefile .toto.py\n' + input;
            }
            IPython.notebook.kernel.execute(cmd);
            cmd = "import os;os.system('gvim .toto.py')";
            IPython.notebook.kernel.execute(cmd);
            return false;
        }}
    );
    
    IPython.keyboard_manager.command_shortcuts.add_shortcut('u', {
        handler : function (event) {
            function handle_output(msg) {
                var ret = msg.content.text;
                IPython.notebook.get_selected_cell().set_text(ret);
            }
            var callback = {'output': handle_output};
            var cmd = "f = open('.toto.py', 'r');print(f.read())";
            IPython.notebook.kernel.execute(cmd, {iopub: callback}, {silent: false});
            return false;
        }}
    );
    

    【讨论】:

    • 这对我来说不适用于 Sublime Text。我改变了 cmd = "import os;os.system('gvim .toto.py')"; to cmd = "import os;os.system('subs .toto.py')";并且 Sublime Text 会打开,但它不会读取 .toto.py。
    • eh 没有启动我的 windows emacs,但我只是让 emacs 自动重新加载临时的“toto”文件。 (更新部分有效)HAPPY!
    【解决方案2】:

    对于使用 IPython终端 应用程序发现此问题的人,有一个内置的键盘快捷键,可以使用当前单元格的内容启动 $EDITOR。保存并退出编辑器会将单元格的内容替换为(但尚未执行)已保存文件的内容。

    默认的键盘快捷键是 F2 键。这对应于 IPython 设置 IPython.terminal.shortcuts.open_input_in_editor

    【讨论】:

    • 有办法改快捷键吗?
    【解决方案3】:

    在@david-brochart 接受的答案的基础上,我将他的代码打包成一个魔术函数,所以现在我只需要在笔记本中运行magic%gvim 即可启用编辑通过 Gvim 为整个笔记本获取任何单元格的内容(我可以在我系统上运行的任何其他笔记本中重复使用相同的线魔法)。

    如果您想做类似的事情,只需在您的 ipython 启动文件夹中创建一个名为 my_magic_functions.py 的文件(您的 ipython 启动路径可能类似于 ~/.ipython/profile_default/startup)然后将以下代码放入该文件中(并保存):

    import IPython.core.magic as ipym
    from IPython import get_ipython
    
    @ipym.magics_class
    class MareBearMagics(ipym.Magics):
        @ipym.line_magic
        def gvim(self, line):
            cell_text = """
    IPython.keyboard_manager.command_shortcuts.add_shortcut('g', {
        handler : function (event) {
            var input = IPython.notebook.get_selected_cell().get_text();
            var cmd = "f = open('.toto.py', 'w');f.close()";
            if (input != "") {
                cmd = '%%writefile .toto.py\\n' + input;
            }
            IPython.notebook.kernel.execute(cmd);
            cmd = "import os;os.system('gvim .toto.py')";
            IPython.notebook.kernel.execute(cmd);
            return false;
        }}
    );
    
    IPython.keyboard_manager.command_shortcuts.add_shortcut('u', {
        handler : function (event) {
            function handle_output(msg) {
                var ret = msg.content.text;
                IPython.notebook.get_selected_cell().set_text(ret);
            }
            var callback = {'output': handle_output};
            var cmd = "f = open('.toto.py', 'r');print(f.read())";
            IPython.notebook.kernel.execute(cmd, {iopub: callback}, {silent: false});
            return false;
        }}
    );
            """
            ipython = get_ipython()
            ipython.run_cell_magic(
                magic_name='javascript', line=None, cell=cell_text)
            print("Cell contents can now be edited via Gvim. From command mode "
                  "use 'g' to open current cell contents in Gvim. After ':wq' "
                  "from Gvim, use 'u' in command mode to update cell contents.")
    
    
    if __name__ == '__main__':
        get_ipython().register_magics(MareBearMagics)
    

    现在启动你的 Jupyter notebook 内核,你应该可以在一个单元格中输入%gvim%(你可以使用自动补全来找到新的魔法命令)然后运行这个单元格来编辑笔记本的单元格Gvim 中的内容。您将收到一条输出消息,让您知道魔术命令已生效:

    现在可以通过 Gvim 编辑单元格内容。在命令模式下使用 'g' 在 Gvim 中打开当前单元格内容。在 Gvim 的 ':wq' 之后,在命令模式下使用 'u' 来更新单元格内容。

    感谢thisstackoverflow 问题中的人们,以及这些人为我提供了将其整合在一起的成分。 :-)

    【讨论】:

      【解决方案4】:

      @david-brochart 的上述代码 sn-p 是一个不错的 hack,但它有几个缺点:

      • 数据很容易丢失,例如不小心按错了单元格上的“u”。
      • 在编辑文件时,Python 内核被阻塞。
      • 内核的全局命名空间被污染。
      • 无法同时编辑多个单元格。
      • 剩余的“.toto.py”文件保留在磁盘上。
      • 文件扩展名不依赖于单元格类型。

      这是解决上述所有问题的改进版本。它仍然是一个 hack(例如,在内核忙碌时无法开始编辑单元格),但它在实践中运行良好。它仍然滥用计算内核来读取和写入文件并启动编辑器,但这样做的方式是尽可能减少副作用。

      要使用这个 sn-p,它必须在 Jupyter 单元中执行。它也可以添加到 ~/.jupyter/custom/custom.js。默认情况下会启动“emacsclient -c”,但这可以被任何其他编辑器替换。只有一个键(默认为“e”)可以将单元格交换为文件并启动编辑器,或者读取文件并将内容重新插入单元格。

      %%javascript
      
      Jupyter.keyboard_manager.command_shortcuts.add_shortcut('e', {
          handler : function (event) {
              function callback(msg) {
                  cell.set_text(msg.content.text);
              }
              var cell = Jupyter.notebook.get_selected_cell();
              // Quote the cell text and *then* double any backslashes.
              var cell_text = JSON.stringify(cell.get_text()).replace(/\\/g, "\\\\");
              var cmd = `exec("""
      cell_text = ${cell_text}
      ext = "${cell.cell_type == 'code' ? 'py' : 'txt'}"
      sep = "#-#-# under edit in file "
      prefix, _, fname = cell_text.partition(sep)
      
      if not fname or prefix:
          # Create file and open editor, pass back placeholder.
          import itertools, subprocess
      
          for i in itertools.count():
              fname = 'cell_{}.{}'.format(i, ext)
              try:
                  with open(fname, 'x') as f:
                      f.write(cell_text)
              except FileExistsError:
                  pass
              else:
                  break
      
          # Run editor in the background.
          subprocess.Popen(['emacsclient', '-c', fname],
                           stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
      
          print(sep, fname, sep='', end='')
      else:
          # Cell has been in edit: read it in and pass it back, and delete it.
          import os
      
          try:
              with open(fname, 'r') as f:
                  cell_text = f.read()
          except FileNotFoundError:
              print("# File {} could not be inserted back.".format(fname), end='')
          else:
              if cell_text.endswith('\\\\n'):
                  cell_text = cell_text[:-1]
              print(cell_text, end='')
              os.remove(fname)
              try:
                  os.remove(fname + '~')
              except FileNotFoundError:
                  pass
      """, None, {})`;
              Jupyter.notebook.kernel.execute(cmd, {iopub: {output: callback}},
                                              {silent: false});
              return false;
          }}
      );
      

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-08
      • 2011-03-18
      • 2011-09-27
      • 1970-01-01
      • 2010-12-07
      相关资源
      最近更新 更多