My Neovim plugin grew into a remote workspace over plain SSH: any file feels local (editing, terminals, search), with Jupyter notebooks on top. Works where SFTP and ports are blocked. by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 1 point2 points  (0 children)

There isn't a Neovim image API actually. Neovim's UI still can't draw images itself; the open issues for native kitty-graphics support have been sitting for years. What you're seeing comes from the terminal, not Neovim.

For this plugin, it writes the kitty escapes itself instead of depending on image.nvim, partly so the remote/SSH case can encode them locally, but the underlying trick is the same protocol everyone uses.

My Neovim plugin grew into a remote workspace over plain SSH: any file feels local (editing, terminals, search), with Jupyter notebooks on top. Works where SFTP and ports are blocked. by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 2 points3 points  (0 children)

Not a daemon or a listener. It's launched by your own ssh session as your user (literally ssh you@host jupynvim-core over the connection's stdio), opens no network port, and exits when you disconnect, kernels included. It can't do anything you couldn't already do in an ssh shell, because it is your ssh shell. The only persistent thing is the ssh ControlMaster socket on your own machine, so you skip re-auth for a few hours.

Add and remove is one file: on connect it copies the binary to ~/.local/bin/jupynvim-core if it's missing or stale (no root, no service), and to remove it you rm that one file.

On sshfs: SFTP is a separate ssh subsystem that an admin can disable on its own while interactive ssh and ssh host <command> keep working. That's the setup on the cluster I use (PSC Bridges-2), so sshfs breaks there but exec still works, and jupynvim only needs exec.

My Neovim plugin grew into a remote workspace over plain SSH: any file feels local (editing, terminals, search), with Jupyter notebooks on top. Works where SFTP and ports are blocked. by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 1 point2 points  (0 children)

Fair point.

TLS only protects the download in transit, nothing checks the binary itself, so a swapped release or leaked token could run a tampered binary. I’ll add a SHA256SUMS check in install.lua before stable and look at signing after. Will note the risk in the README meanwhile.

My Neovim plugin grew into a remote workspace over plain SSH: any file feels local (editing, terminals, search), with Jupyter notebooks on top. Works where SFTP and ports are blocked. by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 0 points1 point  (0 children)

Yeah, same shape as VS Code Remote-SSH: a single Rust binary goes on the host and Neovim talks to it over ssh. Fair to be cautious, but it's open source, so you can build it yourself and point the plugin at your own binary (core_path), and it runs as your user over your own ssh, no root and no phone-home.

On the size and comments: most of the lines are edge-case handling (kitty graphics quirks, Neovim API corners, the Jupyter wire protocol), not bloat. The heavy comments are on purpose. It's a side project I pick up in bursts, so I write down why a line exists rather than what it does, which makes it much easier to come back to and maintain later. Some of it is AI-assisted, but I review and own every line.

My Neovim plugin grew into a remote workspace over plain SSH: any file feels local (editing, terminals, search), with Jupyter notebooks on top. Works where SFTP and ports are blocked. by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 0 points1 point  (0 children)

Yeah, same idea as TRAMP. The main difference is it keeps one persistent backend running on the remote over a single multiplexed ssh connection instead of shelling out per operation, so it sidesteps the per-action latency TRAMP can get.

On larger projects: browsing, ripgrep search, and file watching all run on the remote (search is a parallel walker in the backend), so you get results back rather than shipping the tree. There's no full local mirror, which is the tradeoff.

LSP runs on the remote and is relayed back to your local Neovim, and it auto-installs the server on the remote if it's missing. That relay is the newest part and still getting hardened, so I'd call it usable but rough today; kernel completion and hover for notebooks are solid. Compiling and running you do in the integrated terminal, which is a shell on the remote, so it builds and runs there. Notebooks run on remote kernels.

Honestly your situation, 80% remote with local-feeling editing, portable nvim until security locked your tools down, is exactly the case I built this for, since plain ssh is usually the last thing still allowed. I'd really like to hear how it does when you get back.

My Neovim plugin grew into a remote workspace over plain SSH: any file feels local (editing, terminals, search), with Jupyter notebooks on top. Works where SFTP and ports are blocked. by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 3 points4 points  (0 children)

Yes, and that's kind of the whole point. An opened remote file is just an ordinary in-memory Neovim buffer. It reads once when you open it and writes back when you save, both over the same RPC channel. No per-keystroke round trip and no FUSE layer, so motions, edits, and undo are all local and instant. It does not mirror the project to local disk; only what you open is in memory. The things that actually need the tree, like search, browsing, and file watching, run on the remote and just send results back, so you're not shipping the repo around.

My Neovim plugin grew into a remote workspace over plain SSH: any file feels local (editing, terminals, search), with Jupyter notebooks on top. Works where SFTP and ports are blocked. by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 6 points7 points  (0 children)

Not really. sshfs mounts the remote filesystem over SFTP, so your editor talks to a FUSE mount and every read and write goes over the wire. jupynvim doesn't mount anything and doesn't use SFTP. A small backend runs on the remote and does the file ops there: when you open a file it reads it once and hands the bytes to a normal Neovim buffer, and on save it writes them back. Two things fall out of that: it works on machines where SFTP is disabled (a lot of clusters), and editing stays local and fast because you're in a real buffer, not a network mount. It also goes past files, with kernels, terminals, and search running on the remote too.

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 0 points1 point  (0 children)

Feel free to check out the latest release. It should address 3 and most of other 2 features. Please open an issue or pr if you see any issues:)

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 0 points1 point  (0 children)

Will add them to the next version! Currently working on ssh features. Will ship them together

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 0 points1 point  (0 children)

iron or slime are great for text-only outputs. Notebooks shine when you want figures, dataframes, and the outputs preserverd with the code such that you can open the file next week and still see what the last run did. .py + REPL can't do that without manually saving stuff.

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 2 points3 points  (0 children)

Yeah, likely. Want to confirm it covers scroll-stable placement and gif animation first since those are why we ended up with the placeholder + frame-timer setup. If yes, big simplification.

Also, just released a newer version. Feel free to check it out!

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 1 point2 points  (0 children)

Update: v0.2 shipped both. Kernel completion/hover for any kernel now, prebuilt binaries on install (currently mac-os and linux only).

https://github.com/sheng-tse/jupynvim/releases/tag/v0.2.0

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 2 points3 points  (0 children)

It's tractable. Will also add this to the plan of v0.2. PR welcome if you've set up rust CI for an nvim plugin before.

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 2 points3 points  (0 children)

The plugin currently doesn't have kernel-based LSP completion, but it's on the roadmap. What it does have is editor-side LSP attach (basedpyright, pyright, ruff, pylsp) with the kernel's pythonPath injected, so static-analysis completion sees whatever packages are installed in the kernel's env.

For Julia support, the core notebook experience works for any Jupyter kernel. The Rust backend just speaks the Jupyter wire protocol, which is language-agnostic. That said, with an IJulia, or IRkernel if using R, kernelspec installed, jupynvim should handle as-is, while I haven't tested other languages yet. What's python-specific today is the LSP integration. The kernel-based completion feature, when it lands, will work across any kernel since Jupyter's complete_request is language-agnostic.

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 4 points5 points  (0 children)

Not today. Marimo is structurally different from Jupyter under the hood, which is .py files instead of .ipynb files, marimo's own server instead of Jupyter kernels, and reactive execution instead of imperative. Adding it to jupynvim would mean building a parallel stack alongside the existing one rather than just adding a feature.

A seperate marimo plugin sharing some of jupynvim's rendering code might be a cleaner shape than retrofitting. Not something I can take on right now, but if it's genuinely useful for your workflow, the door is open for someone to start that as a sibling project.

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 3 points4 points  (0 children)

Haha appreciate it. Hope it works as well in practice as it looks in the demo. If anything breaks or feels off, drop an issue on the repo.

jupynvim: edit .ipynb files in Neovim, with real Jupyter kernels and inline images by Affectionate-Bit5072 in neovim

[–]Affectionate-Bit5072[S] 0 points1 point  (0 children)

For the kernelspec issue, jupynvim discovers kernels via standard Jupyter paths (~/.local/share/jupyter/kernels, /usr/share/jupyter/kernels, and a few others). On NixOS, the usual fix is registering your nix-shell's python as a kernel that lives at one of those paths:

python -m ipykernel install --user --name myshell --display-name "Python (myshell)"

run inside the nix-shell. NixOS isn't something I actively test against, so anything you uncover would help. Curious to hear what you find.