The bridge between Neovim and Jupyter Lab, edit in Neovim and preview/run in Jupyter Lab.
This project includes two parts: a JupyterLab extension and a Neovim plugin
JupyterLab extension exposes functions of Jupyter lab, and provides a remote procedure call(RPC) serviceNeovim plugin calls the RPC service when it receives events from Neovim via autocmdThis project provides two work modes for different network environments. If the browser where your jupyter lab is
located cannot directly access nvim, you must use proxy mode; If you need to collaborate and use the same Jupyter with
others, you must use direct mode
direct mode: (default, recommended) In this mode, neovim is server and neovim plugin(neopyter) is listening to remote_address,
the browser where jupyter lab is located will connect to neovim
proxy mode: In this mode, Jupyter lab server(server side, the host you run jupyter lab to start JupyterLab) is server
and jupyter lab server extension(neopyter) is listening to {IP}:{Port}`, the neovim plugin(neopyter) will connect to {IP}:{Port}`
Ultimately, Neopyter can control Juppyter lab. Neopyter can implement abilities like jupynium.nvim.
[!NOTE] More screenshots and spec, please refer to doc/specification.ipynb and doc/specification.ju.py
nvim-lua/plenary.nvimAbaoFromCUG/websocket.nvim (optional for mode="direct")Neopyter support two parts, so we need to install them separately.
To install the jupyterlab extension, execute:
pip install neopyter
Configure JupyterLab in side panel
mode: Refer to the previous introduction about modeIP: If mode=proxy, set to the IP of the host where jupyter server is located. If proxy=direct, set to the IP of the
host where neovim is locatedPort: Idle port of the IP's' hostNOTICE: all settings is saved to localStorage
{
"SUSTech-data/neopyter",
dependencies = {
'nvim-lua/plenary.nvim',
'nvim-treesitter/nvim-treesitter', -- neopyter don't depend on `nvim-treesitter`, but does depend on treesitter parser of python
'AbaoFromCUG/websocket.nvim', -- for mode='direct'
},
---@type neopyter.Option
opts = {
mode="direct",
remote_address = "127.0.0.1:9001",
file_pattern = { "*.ju.*" },
on_attach = function(bufnr)
-- do some buffer keymap
end,
},
}
---@type neopyter.Option
local default_config = {
remote_address = "127.0.0.1:9001",
file_pattern = { "*.ju.*" },
filename_mapper = function(ju_path)
local ipynb_path = vim.fn.fnamemodify(ju_path, ":r:r:r") .. ".ipynb"
if is_windows then
ipynb_path = ipynb_path:gsub("\\", "/")
end
return ipynb_path
end,
--- auto attach to buffer
auto_attach = true,
--- auto connect with remote jupyterlab
auto_connect = true,
mode = "direct",
---@type neopyter.JupyterOption # ref `:h neopyter.JupyterOption`
jupyter = {
auto_activate_file = true,
partial_sync = false,
-- Always scroll to the current cell.
scroll = {
enable = true,
align = "center",
},
},
---@type neopyter.HighlightOption # ref `:h neopyter.HighlightOption`
highlight = {
enable = true,
mode = "separator",
},
---@type neopyter.TextObjectOption # ref `:h neopyter.TextObjectOption`
textobject = {
enable = true,
-- more capture, poorer performance
queries = { "cellseparator", "cellcontent", "cell" },
},
---@type neopyter.InjectionOption # ref `:h neopyter.InjectionOption`
injection = {
enable = true,
},
---@type neopyter.ParserOption # ref `:h neopyter.ParserOption`
parser = {
trim_whitespace = false,
python = {},
r = {},
},
}
See :h neopyter-configuration-types for all option type description.
Suggest keymaps(neopyter don't provide default keymap):
on_attach = function(buf)
local function map(mode, lhs, rhs, desc)
vim.keymap.set(mode, lhs, rhs, { desc = desc, buffer = buf })
end
-- same, recommend the former
map("n", "<C-Enter>", "<cmd>Neopyter execute notebook:run-cell<cr>", "run selected")
-- map("n", "<C-Enter>", "<cmd>Neopyter run current<cr>", "run selected")
-- same, recommend the former
map("n", "<space>X", "<cmd>Neopyter execute notebook:run-all-above<cr>", "run all above cell")
-- map("n", "<space>X", "<cmd>Neopyter run allAbove<cr>", "run all above cell")
-- same, recommend the former, but the latter is silent
map("n", "<space>nt", "<cmd>Neopyter execute kernelmenu:restart<cr>", "restart kernel")
-- map("n", "<space>nt", "<cmd>Neopyter kernel restart<cr>", "restart kernel")
map("n", "<S-Enter>", "<cmd>Neopyter execute notebook:run-cell-and-select-next<cr>", "run selected and select next")
map("n", "<M-Enter>", "<cmd>Neopyter execute notebook:run-cell-and-insert-below<cr>", "run selected and insert below")
map("n", "<F5>", "<cmd>Neopyter execute notebook:restart-run-all<cr>", "restart kernel and run all")
end
jupyter lab, there is a sidebar named Neopyter, which display neopyter ip+port*.ju.py file in neovim# %% in Neovim to create a code cell.:Neopyter status alias to :checkhealth neopyter currently:Neopyter connect [remote 'ip:port'], e.g. :Neopyter command 127.0.0.1:9001, connect Jupyter lab manually:Neopyter disconnect:Neopyter sync current, make sync current *.ju.* file with the currently open *.ipynb:Neopyter sync [filename], e.g. :Neopyter sync main.ipynb:Neopyter run current, same as Run>Run Selected Cell and Do not Advance menu in Jupyter lab:Neopyter run allAbove, same as Run>Run All Above Selected Cell menu in Jupyter lab:Neopyter run allBelow, same as Run>Run Selected Cell and All Below menu in Jupyter lab:Neopyter run all, same as Run>Run All Cells menu in Jupyter lab:Neopyter kernel restart, same as Kernel>Restart Kernel menu in Jupyter lab:Neopyter kernel restartRunAll, same as Kernel>Restart Kernel and Run All Cells menu in Jupyter lab:Neopyter execute [command_id] [args], execute Jupyter lab's
command
directly, e.g. :Neopyter execute notebook:export-to-format {"format":"html"}If neoconf.nvim is available, neopyter will automatically register/read neoconf settings
{
"neopyter": {
"mode": "proxy",
"remote_address": "127.0.0.1:9001"
}
}
nvim-cmplspkind.nvim
local lspkind = require("lspkind")
local cmp = require("cmp")
cmp.setup({
sources = cmp.config.sources({
-- default: all source, maybe some noice
{ name = "neopyter" },
-- { name = "neopyter", option={ source = { "CompletionProvider:kernel" } } },
}),
formatting = {
format = lspkind.cmp_format({
mode = "symbol_text",
menu = {
buffer = "[Buf]",
nvim_lsp = "[LSP]",
nvim_lua = "[Lua]",
neopyter = "[Neopyter]",
},
symbol_map = {
-- specific complete item kind icon
["Magic"] = "🪄",
["Path"] = "📁",
["Dict key"] = "🔑",
["Instance"] = "",
["Statement"] = "",
},
}),
},
)}
-- menu item highlight
vim.api.nvim_set_hl(0, "CmpItemKindMagic", { bg = "NONE", fg = "#D4D434" })
vim.api.nvim_set_hl(0, "CmpItemKindPath", { link = "CmpItemKindFolder" })
vim.api.nvim_set_hl(0, "CmpItemKindDictkey", { link = "CmpItemKindKeyword" })
vim.api.nvim_set_hl(0, "CmpItemKindInstance", { link = "CmpItemKindVariable" })
vim.api.nvim_set_hl(0, "CmpItemKindStatement", { link = "CmpItemKindVariable" })
More information, see nvim-cmp wiki
blink.cmprequire("blink-cmp").setup({
sources = {
per_filetype = {
python = { inherit_defaults = true, "neopyter" },
},
providers = {
neopyter = {
name = "Neopyter",
module = "neopyter.blink",
---@type neopyter.BlinkCompleterOption
opts = {},
},
},
},
})
Neopyter load textobjects.scm dynamic according config.textobject.queries:
{
"SUSTech-data/neopyter",
---@type neopyter.Option
opts = {
textobject = {
enable = true,
queries = {
"linemagic",
"cellseparator",
"cellcontent",
"cell"
},
},
},
}
The more queries you added, the poorer performance to capture, so only add what you need.
Then you can config you nvim-treesitter-textobjects as usually:
require'nvim-treesitter.configs'.setup {
textobjects = {
select = {
enable = true,
lookahead = true,
keymaps = {
["aj"] = { query = "@cell", desc = "Select cell" },
["ij"] = { query = "@cellcontent", desc = "Select cell content" },
},
},
move = {
enable = true,
goto_next_start = {
["]j"] = "@cellseparator",
},
goto_previous_start = {
["[j"] = "@cellseparator",
},
},
},
}
Supported queries:
@linemagic@cellseparator@cellseparator.code@cellseparator.markdown@cellseparator.raw@cellcontent@cellNeopyter provides rich lua APIs, you could use below code as initialization:
-- Reference to `:h neopyter-jupyterlab-api` for all api document
local current_lab = require("neopyter.jupyter").jupyterlab
current_lab:execute_command("notebook:export-to-format", {format="html"})
-- Reference to `:h neopyter-notebook-api` for all api document
local current_notebook = require("neopyter.jupyter").notebook
current_notebook:run_selected_cell()
current_notebook:run_all_above()
current_notebook:run_all_below()
:h neopyter-notebook-api:h neopyter-jupyterlab-api-apiNotebook and JupyterLab APIs are wrapped by async context automatically.
vim.defer_fn(function()
-- non-async context, API response may be unordered
current_notebook:run_selected_cell()
current_notebook:run_all_above()
current_notebook:run_all_below()
end, 0)
require("neopyter.async").run(function()
-- async context, so which will call and return in order
current_notebook:run_selected_cell()
current_notebook:run_all_above()
current_notebook:run_all_below()
end)
.ipynb manuallyip:portRpcClient, support async RPC request
vim.rpcrequest and vim.rpcnotifyhighlights and textobjects querieshighlights, textobjects, parser to unified parserzen highlight is inspired by snacks.zen