💻

Neovim+LazyvimでPython REPL(iPython)を使いやすくする

2024/05/02に公開

モチベーション

Neovim + LazyvimでのPythonの開発環境を構築中。LazyVimのオプションを使えばかなり簡単にそれなりのIDEっぽい環境はすぐに構築が可能だった。

一方、VS Codeで実現できていたJupyterNotebookの実行や、トライアンドエラーに便利なインタラクティブな実行環境はなかなか構築が難しい。

一時期はmolten.nvimを使ったインラインっぽい(JupyterNoteライクな)環境を試していたが、自分の環境では一部の動作が期待とおりでなかったり苦労していた。
JupyterNotebookの実行は機会が少ないので、VS Code+VS code Neovim Extensionの利用(もしくはブラウザでの実行)を選択するとして、TerminalでiPythonを使う方法を探ることにした。

iron.nvimの設定

各方面を検索したところ、NeovimからREPLを呼びだす iron.nvim を使う方法がよさそうなので設定を検討した。
Vigemus/iron.nvim: Interactive Repl Over Neovim

LazyVim での設定はこちらのブログを参考に設定してみる。
How to run Python on Neovim like Jupyter - DEV Community

plugin フォルダに iron_repl.lua ファイルを設置してpluginの設定。

iron_repl.lua
return {
  "hkupty/iron.nvim",
  config = function()
    local iron = require("iron.core")

    iron.setup({
      config = {
        scratch_repl = true,
        repl_definition = {
          python = {
            command = { "ipython" },
            format = require("iron.fts.common").bracketed_paste,
          },
        },
        repl_open_cmd = require("iron.view").right(90),
      },
      -- If the highlight is on, you can change how it looks
      -- For the available options, check nvim_set_hl
      highlight = {
        italic = true,
      },
      ignore_blank_lines = true,
    })
  end,
}

Keymaps

iron を導入しただけではkeybindingが一切設定されていない。
公式の設定を参考に iron_repl.lua に以下を設定。

工夫したポイント

  1. VS Codeで便利に使っていた「対話型ウィンドウで選択範囲/行を実行」コマンドと同じ挙動する関数を作成し、同コマンドにアサインしていた Control + Enter をアサイン
  2. Which-keyでコマンドが解りやすいように各キーバインドに desc を設定
  3. 一部[1]のコマンドのキーアサインに function() ~ end の設定が必要で、ソースファイルの設定を確認しながら、ひとつひとつ潰していった
keymaps.lua
return {
  "hkupty/iron.nvim",
  config = function()
    local core = require("iron.core")
    local marks = require("iron.marks")
    local visual_send_and_move_down = function()
      core.visual_send()
      vim.cmd("normal! j")
    end
    local line_send_and_move_down = function()
      core.send_line()
      vim.cmd("normal! j")
    end
-- ここに上記のsetupの記述が入る。
    vim.keymap.set("n", "<leader>rs", "<cmd>IronRepl<cr>", { desc = "Start Repl" })
    vim.keymap.set("n", "<leader>rr", "<cmd>IronRestart<cr>", { desc = "Restart Repl" })
    vim.keymap.set("n", "<leader>rF", "<cmd>IronFocus<cr>", { desc = "Focus Repl" })
    vim.keymap.set("n", "<leader>rh", "<cmd>IronHide<cr>", { desc = "Hide Repl" })
    vim.keymap.set("n", "<leader>rc", core.send_motion, { desc = "Send motion" })
    vim.keymap.set("n", "<leader>rf", core.send_file, { desc = "Send file" })
    vim.keymap.set("n", "<leader>rl", core.send_line, { desc = "Send line" })
    vim.keymap.set("n", "<C-CR>", line_send_and_move_down, { desc = "Send line" })
    vim.keymap.set("n", "<leader>rms", core.send_mark, { desc = "Mark send" })
    vim.keymap.set("n", "<leader>rmc", core.mark_motion, { desc = "Mark motion" })
    vim.keymap.set("n", "<leader>rq", core.close_repl, { desc = "Exit" })
    vim.keymap.set("n", "<leader>rmd", marks.drop_last, { desc = "Mark delete" })
    vim.keymap.set("n", "<leader>r<CR>", function()
      core.send(nil, string.char(13))
    end, { desc = "Carriage return" })
    vim.keymap.set("n", "<leader>r<space>", function()
      core.send(nil, string.char(03))
    end, { desc = "Interrupt" })
    vim.keymap.set("n", "<leader>rx", function()
      core.send(nil, string.char(12))
    end, { desc = "Clear" })
    vim.keymap.set("v", "<leader>rc", core.visual_send, { desc = "Send visual" })
    vim.keymap.set("v", "<C-CR>", visual_send_and_move_down, { desc = "Send visual" })
    vim.keymap.set("v", "<leader>rmc", core.mark_visual, { desc = "Mark visual" })
  end,
}
脚注
  1. <CR>, Interrupt, Clear ↩︎

Discussion