diff --git a/README.md b/README.md index 5aa1658..23fc4d8 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,9 @@ pyrepl.setup({ preferred_kernel = "python3", -- automatically prompt to convert notebook files into python scripts jupytext_hook = true, + -- auto-install missing runtime packages on `:PyreplOpen` + -- set to "pip" or "uv" to enable; false to disable + auto_install = false, }) -- repl ui-related commands @@ -86,6 +89,13 @@ Then install REPL runtime packages with `uv` or `pip` directly from Neovim: :PyreplInstall uv ``` +Alternatively, enable `auto_install` to have pyrepl silently install missing packages +on `:PyreplOpen`: + +```lua +require("pyrepl").setup({ auto_install = "uv" }) -- or "pip" +``` + To use jupytext integration, make sure jupytext is available in Neovim: ```bash diff --git a/doc/pyrepl.txt b/doc/pyrepl.txt index 1e71c6c..2a4236b 100644 --- a/doc/pyrepl.txt +++ b/doc/pyrepl.txt @@ -62,6 +62,9 @@ Minimal `vim.pack` setup with the default config and example keymaps: preferred_kernel = "python3", -- automatically prompt to convert notebook files into python scripts jupytext_hook = true, + -- auto-install missing runtime packages on `:PyreplOpen` + -- set to "pip" or "uv" to enable; false to disable + auto_install = false, }) -- repl ui-related commands @@ -238,6 +241,7 @@ Default configuration: python_path = "python", preferred_kernel = "python3", jupytext_hook = true, + auto_install = false, }) < @@ -293,6 +297,9 @@ Options: Default: "python3". jupytext_hook (boolean) Enable jupytext hook to automatically convert notebook buffers to `py:percent`. Default: true. + auto_install (false | "pip" | "uv") + When |PyreplOpen| is invoked and required runtime packages are + missing, install them using the specified tool. Default: false. ============================================================================== TIPS & TRICKS *pyrepl-tips* diff --git a/lua/pyrepl/config.lua b/lua/pyrepl/config.lua index 95b9913..821535b 100644 --- a/lua/pyrepl/config.lua +++ b/lua/pyrepl/config.lua @@ -14,6 +14,7 @@ local defaults = { python_path = "python", preferred_kernel = "python3", jupytext_hook = true, + auto_install = false, } local image_provider_cache diff --git a/lua/pyrepl/init.lua b/lua/pyrepl/init.lua index c0c4ef1..643fc45 100644 --- a/lua/pyrepl/init.lua +++ b/lua/pyrepl/init.lua @@ -9,9 +9,10 @@ local send = require("pyrepl.send") local group = vim.api.nvim_create_augroup("Pyrepl", { clear = true }) ----@param args table|nil -function M.open_repl(args) - core.open_repl(args) +function M.open_repl() + python.ensure_dependencies(function() + core.open_repl() + end) end function M.hide_repl() diff --git a/lua/pyrepl/python.lua b/lua/pyrepl/python.lua index 556c38d..a6c8198 100644 --- a/lua/pyrepl/python.lua +++ b/lua/pyrepl/python.lua @@ -203,4 +203,67 @@ function M.get_tool_completions(arglead) end, vim.tbl_keys(tools)) end +---Check if required Python packages are importable. +---@return boolean +function M.check_dependencies() + local ok, python_path = pcall(M.get_python_path) + if not ok then + return false + end + + local obj = vim.system( + { python_path, "-c", "import jupyter_console, pynvim" }, + { text = true } + ):wait() + + return obj.code == 0 +end + +---Check dependencies and, if `auto_install` is set, install them silently. +---Calls callback only after dependencies are confirmed present. +---@param callback fun() +function M.ensure_dependencies(callback) + if M.check_dependencies() then + callback() + return + end + + local tool = config.get_state().auto_install + if not tool or not tools[tool] then + vim.notify( + config.get_message_prefix() + .. "dependencies missing, run `:PyreplInstall pip|uv` or set `auto_install`", + vim.log.levels.ERROR + ) + return + end + + local ok, python_path = pcall(M.get_python_path) + if not ok then + vim.notify(python_path, vim.log.levels.ERROR) + return + end + + local packages_string = table.concat(packages, " ") + local cmd_str = tools[tool]:format(python_path) .. " " .. packages_string + + vim.notify(config.get_message_prefix() .. "installing dependencies...") + + vim.system( + { "/bin/sh", "-c", cmd_str }, + { text = true }, + vim.schedule_wrap(function(obj) + if obj.code == 0 then + vim.notify(config.get_message_prefix() .. "dependencies installed") + callback() + else + vim.notify( + config.get_message_prefix() .. "installation failed\n" .. (obj.stderr or ""), + vim.log.levels.ERROR + ) + end + end) + ) +end + return M diff --git a/lua/pyrepl/types.lua b/lua/pyrepl/types.lua index 47b6fa5..e856d80 100644 --- a/lua/pyrepl/types.lua +++ b/lua/pyrepl/types.lua @@ -14,6 +14,7 @@ ---@field python_path string|nil ---@field preferred_kernel string|nil ---@field jupytext_hook boolean +---@field auto_install false | "pip" | "uv" ---Plugin setup opts (all arguments are optional). ---@class pyrepl.ConfigOpts @@ -29,6 +30,7 @@ ---@field python_path? string ---@field preferred_kernel? string ---@field jupytext_hook? boolean +---@field auto_install? false | "pip" | "uv" ---Image provider interface. ---@class pyrepl.Image