Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ccf0402
feat!(kernel): remove Kernel.metadata
wurli Sep 12, 2026
9737c36
feat(kernel): add Kernel.subclass
wurli Sep 14, 2026
e001c51
docs(readme): reword ai FAQ
wurli Sep 14, 2026
3abb9f9
fix(filter_kernels): normalise spec path before comparing
wurli Sep 14, 2026
0e98632
chore(build): bump version to 0.2.0
wurli Sep 14, 2026
dcb6d98
feat!(kernel): add Kernel.windows
wurli Sep 17, 2026
56184a6
refactor: move kernel buffers into Kernel.bufs
wurli Sep 17, 2026
310d057
docs: make docs
wurli Sep 17, 2026
22a8ced
fix: broken image open
wurli Sep 17, 2026
b80fa45
fix(send): terminal auto-focus
wurli Sep 17, 2026
a6031e2
refactor: misc tidying
wurli Sep 17, 2026
26cea92
fix(emmylua): missing type annotation
wurli Sep 17, 2026
4d00ba0
fix(emmylua): type issue
wurli Sep 18, 2026
882600e
chore(ci): don't use emmylua_check --format github
wurli Sep 18, 2026
7272f04
fix(emmylua): point to $VIMRUNTIME/lua
wurli Sep 18, 2026
309b9a8
chore(emmylua): remove unneeded annotation
wurli Sep 18, 2026
42659fe
fix(emmylua): missing annotation (nvim nightly)
wurli Sep 18, 2026
ed4a29b
fix(test): give test_img a bit more time
wurli Sep 18, 2026
790dbc9
fix(tests): use kernel.buf.term, not kernel.term
wurli Sep 18, 2026
6cadcd7
docs: document new API
wurli Sep 18, 2026
e0b548f
refactor: highlight namespaces
wurli Sep 18, 2026
f648ff1
feat(hooks): add hooks.on_win_open
wurli Sep 18, 2026
c610910
feat!(win): don't handle jet.Buf objects
wurli Sep 18, 2026
1e72d80
docs: show how to open plots in floating window
wurli Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .emmyrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json",
"diagnostics": {
"disable": [
"unnecessary-if"
"unnecessary-if",
"unnecessary-assert"
]
},
"runtime": {
Expand All @@ -15,20 +16,20 @@
"workspace": {
"ignoreDir": [
"deps",
".venv"
".venv"
],
"ignoreGlobs": [
"*.log"
],
"library": [
"$VIMRUNTIME",
"$VIMRUNTIME/lua",
"./deps",
"$HOME/.local/share/nvim/lazy/image.nvim/",
"$HOME/.local/share/nvim/lazy/snacks.nvim/",
"$HOME/.local/share/nvim/lazy/lualine.nvim/"
],
"workspaceRoots": [
"./lua"
]
"workspaceRoots": [
"./lua"
]
}
}
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ jobs:
- name: Run emmylua_check
run: |
export VIMRUNTIME=$(nvim --clean --headless +'lua io.write(vim.env.VIMRUNTIME)' +qa)
emmylua_check . --warnings-as-errors --output-format github
emmylua_check . --warnings-as-errors

# Ark needs a real R at runtime. Install it out-of-band; the script only
# downloads the ark binary and writes the kernelspec.
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,10 +438,9 @@ Architecture differences aside, a high-level feature comparison is as follows:
## FAQ

<details>
<summary>Was jet.nvim vibe coded?</summary>
<summary>Is jet.nvim vibe coded?</summary>

> No. I did use AI quite a bit to develop Jet proper, i.e. the Rust backend. I
> wrote this README with a keyboard using my own two mucky paws.
> No. Some AI was used to develop Jet proper, jet.nvim's Rust backend.

</details>

Expand Down
83 changes: 74 additions & 9 deletions doc/jet-nvim.txt
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ The full set of configuration options is as follows:
After a kernel becomes or stops
becoming the 'current' kernel for its
filetype.
• {on_win_open} (`table<any,fun(k: jet.Kernel, win: jet.Win, buf: integer)>`)
After a window is opened

------------------------------------------------------------------------------
Extensions *jet-extensions*
Expand Down Expand Up @@ -438,7 +440,7 @@ THE API MODULE *jet-jet.api*
api.next_expr_boundary({opts}, {p}) *jet-api.next_expr_boundary()*

Parameters: ~
• {opts} (`jet.send.next_expr_boundary.Opts?`)
• {opts} (`jet.send.next_expr_boundary.Opts?`) Fields:
• direction (`(1|-1)?`)
• boundary (`("start"|"end"|"any")?`)
• current_ok (`boolean?`)
Expand Down Expand Up @@ -577,8 +579,6 @@ THE KERNEL CLASS *jet-the-kernel-class*
connection to the kernel.
• {lsp} (`jet.Lsp`) Information about the kernel's Jet
LSP process.
• {term} (`jet.Kernel.Term?`) Repl buffer data
• {img} (`jet.Kernel.Img?`) Image buffer data
• {owned} (`boolean`) `true` if the kernel session was
started by this nvim session; `false` otherwise.
• {filetype} (`string?`) The kernel's filetype. jet.nvim will
Expand Down Expand Up @@ -607,20 +607,32 @@ THE KERNEL CLASS *jet-the-kernel-class*
• {hooks} (`jet.Hooks`) Kernel-specific hooks. Basically
for convenience on top of the 'global' hooks you
can set in |jet.Config|
• {metadata} (`table<string,any>`) Arbitrary extra data,
e.g. for use by extensions
• {priority} (`integer`) Defaults to 100. `api.get_kernel()`
will try to use this field to select a single
kernel. Typically you would set the priority
using the kernel's init hook (see |jet.Hooks|).
• {wins} (`jet.Kernel.Windows`) Windows used to show
kernel UI. Kernels use 2 windows by default (one
for the repl, one for all other stuff). This
field is extensible!
Fields:
• primary (`jet.Win`)
• secondary (`jet.Win`)
• {bufs} (`jet.Kernel.Buffers`) Buffers used to show
kernel UI. By default includes the repl and a
plots buffer.
Fields:
• term (`jet.Kernel.Term?`)
• img (`jet.Kernel.Img?`)
• {subclass} (`string?`)

Kernel.init_owned({opts}) *jet-Kernel.init_owned()*

Initialise a `Kernel` object which will start its own "owned" jupyter
process.

Parameters: ~
• {opts} (`jet.kernel.init_owned.Opts`)
• {opts} (`jet.kernel.init_owned.Opts`) Fields:
• spec_path (`string`)
• session_name (`string?`)
• spec
Expand All @@ -636,7 +648,7 @@ Kernel.init_external({opts}) *jet-Kernel.init_external()*
session

Parameters: ~
• {opts} (`jet.kernel.init_external.Opts`)
• {opts} (`jet.kernel.init_external.Opts`) Fields:
• session_id (`string`)
• priority (`integer?`)

Expand Down Expand Up @@ -748,6 +760,18 @@ Kernel:stop({callback}) *jet-Kernel:stop()*
See: ~
• |Kernel:close()|

Kernel:get_comm({name}, {pattern}) *jet-Kernel:get_comm()*

Convenience; find a comm id (from `Kernel.open_comms`) using the comm name

Parameters: ~
• {name} (`string`)
• {pattern} (`boolean?`)

Return: ~
(`string?`)
(`table?`)

Kernel:comm_open({name}, {data}, {opts}) *jet-Kernel:comm_open()*

Open a comm channel to the kernel
Expand All @@ -761,7 +785,7 @@ Kernel:comm_open({name}, {data}, {opts}) *jet-Kernel:comm_open()*
Parameters: ~
• {name} (`string`)
• {data} (`table?`)
• {opts} (`jet.kernel.comm_open.Opts?`)
• {opts} (`jet.kernel.comm_open.Opts?`) Fields:
• listener (`(fun(res: jupyter.Msg))?`)
• listener_interval (`integer?`): In milliseconds,
default 50ms
Expand Down Expand Up @@ -850,7 +874,7 @@ jet.nvim uses the following highlight groups, which can be overridden:
• {JetIdle} (`{ link = "DiagnosticOk" }`)
• {JetItalic} (`{ italic = true }`)
• {JetLabel} (`{ link = "@label" }`)
• {JetRepl} (`{ link = "NormalFloat" }`)
• {JetNormal} (`{ link = "NormalFloat" }`)
• {JetSpecial} (`{ link = "@punctuation.special" }`)
• {JetSuccess} (`{ link = "DiagnosticOk" }`)
• {JetUrl} (`{ link = "@markup.link" }`)
Expand Down Expand Up @@ -916,4 +940,45 @@ tree-sitter node: >lua
jet.filetype.python = { get_expr = get_python_expr }
<

CUSTOM UI COMPONENTS *jet-custom-ui-components*

Extensions may want to add custom UI for a particular kernel. `Kernel` objects
manage UI via two fields:

• `Kernel.wins`: a table with the following fields:
• `wins.primary` (|jet.Win|)
• `wins.secondary` (|jet.Win|)
• `Kernel.bufs` a table with the following fields
• `bufs.term`: (|jet.Kernel.Term|, inherits from |jet.Buf|) targets the
`primary` window
• `bufs.img`: (|jet.Kernel.Img|, inherits from |jet.Buf|) targets the
`secondary` window

You can add more fields to `Kernel.wins` or `Kernel.bufs` to define custom UI,
or modify existing fields to control behaviour. For example, to open the
secondary window in a float: >lua
local hooks = require("jet").hooks

-- Patch the secondary window's `open_opts` to open a float
---@param k jet.Kernel
table.insert(hooks.on_kernel_init, function(k)
---@type vim.api.keyset.win_config
k.wins.secondary.open_opts = {
relative = "editor",
col = math.floor(vim.o.columns * 0.1),
width = math.floor(vim.o.columns * 0.8),
row = math.floor(vim.o.lines * 0.1),
height = math.floor(vim.o.lines * 0.8),
}
end)

-- Focus the window when it opens and add a keymap to close
table.insert(k.hooks.on_win_open, function(_, win, buf)
if win:name() == "secondary" then
vim.keymap.set("n", "q", "<cmd>q<cr>", { buf = buf })
vim.api.nvim_set_current_win(assert(win:winnr()))
end
end)
<

vim:tw=78:sw=4:ts=4:ft=help:norl:et:
5 changes: 4 additions & 1 deletion doc/jet-recipes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ visible. >lua

local code = input and input.code
local text = result and result.data and result.data["text/plain"]
local term_is_open = k.term and k.term:win()
local term_is_open = k.buf.term and k.buf.term:win()

if code and text and not term_is_open then
vim.notify(string.format("Ran `%s`:\nResult: %s", code, text))
Expand Down Expand Up @@ -137,4 +137,7 @@ alternative approach, e.g. for R, would be to override the `print` method for
console. jet.nvim could then pick up the `execute_reply` message and continue
to call visidata as above.

------------------------------------------------------------------------------
Floating plots window *jet-recipes-floating-plots-window*

vim:tw=78:sw=4:ts=4:ft=help:norl:et:
112 changes: 93 additions & 19 deletions doc/jet-types.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Range:code({opts}) *jet-types-Range:code()*
Also returns the local filetype at the range start.

Parameters: ~
• {opts} (`jet.send.range_code.Opts?`)
• {opts} (`jet.send.range_code.Opts?`) Fields:
• comments (`boolean?`): Set to `true` to include
comments in the returned code.

Expand Down Expand Up @@ -161,9 +161,8 @@ Repl and image buffers *jet-types-repl-and-image-buffers*
Term.init({opts}) *jet-types-Term.init()*

Parameters: ~
• {opts} (`jet.Kernel.Term.init.Opts`)
• {opts} (`jet.Kernel.Term.init.Opts`) Fields:
• kernel (`jet.Kernel`)
• ns (`integer`)

Return: ~
(`jet.Kernel.Term`)
Expand All @@ -185,9 +184,8 @@ Term:send({code}, {tabstop}) *jet-types-Term:send()*
Img.init({opts}) *jet-types-Img.init()*

Parameters: ~
• {opts} (`jet.Kernel.Img.init.Opts`)
• {opts} (`jet.Kernel.Img.init.Opts`) Fields:
• kernel (`jet.Kernel`)
• ns (`integer`)

Return: ~
(`jet.Kernel.Img`)
Expand All @@ -212,42 +210,43 @@ Img:display({which}) *jet-types-Img:display()*
Parameters: ~
• {which} (`(string|integer)?`)

------------------------------------------------------------------------------
Buffer/window abstractions *jet-types-bufferwindow-abstractions*

*jet.Buf*

Fields: ~
• {buf} (`integer`)
• {name} (`string`)
• {augroup} (`integer`)
• {ns} (`integer`)
• {kernel} (`jet.Kernel`)
• {open_opts} (`(vim.api.keyset.win_config|fun() -> vim.api.keyset.win_config)`)
• {buf} (`integer`)
• {win_name} (`string`)
• {name} (`string`)
• {augroup} (`integer`)
• {ns} (`integer`)
• {kernel} (`jet.Kernel`)

Buf.init({class}, {opts}) *jet-types-Buf.init()*

Parameters: ~
• {class} (`T?`)
• {opts} (`jet.Buf.init.Opts`)
• {opts} (`jet.Buf.init.Opts`) Fields:
• name (`string`)
• ns (`integer`)
• win_name (`(string|keyof jet.Kernel.Windows)`)
• kernel (`jet.Kernel`)
• open_opts
(`(vim.api.keyset.win_config|fun() -> vim.api.keyset.win_config)`)

Return: ~
(`T`)

Buf:win() *jet-types-Buf:win()*

Get a window displaying the buffer, if there is one
Get the kernel window associated with the buffer

Return: ~
(`integer?`)
(`jet.Win`)

Buf:open({focus}, {opts}) *jet-types-Buf:open()*
Buf:open({opts}, {focus}) *jet-types-Buf:open()*

Parameters: ~
• {focus} (`boolean?`)
• {opts} (`vim.api.keyset.win_config?`)
• {focus} (`boolean?`)

Return: ~
(`integer`) The opened window
Expand All @@ -262,4 +261,79 @@ Buf:create_autocmd({event}, {callback}) *jet-types-Buf:create_autocmd()*
• {event} (`(vim.api.keyset.events|vim.api.keyset.events[])`)
• {callback} (`(string|fun(args: vim.api.keyset.create_autocmd.callback_args) -> boolean?)`)

*jet.Win*

Fields: ~
• {ns} (`integer`)
• {focus} (`boolean`) If `true` then `Win:open()` will focus an
already open window, otherwise it's a no-op.
• {kernel} (`jet.Kernel`)
• {open_opts} (`(vim.api.keyset.win_config|fun(k: jet.Kernel) -> vim.api.keyset.win_config)?`)
• {__index} (`jet.Win`)

Win.init({opts}) *jet-types-Win.init()*

Parameters: ~
• {opts} (`jet.Win.init.Opts`) Fields:
• kernel (`jet.Win["kernel"]`)
• open_opts (`jet.Win["open_opts"]`)
• focus (`jet.Win["focus"]`)

Return: ~
(`jet.Win`)

Win:name() *jet-types-Win:name()*

Return: ~
(`string`)

Win:open({buf}, {opts}, {focus}) *jet-types-Win:open()*

Opens with the following algorithm

• If the window is not open, opens with `buf`
• If the window is open with `buf`, focus the window (if `Win.focus`)
• Otherwise, if the window is open with another buffer:
• Does the buffer have `vim.b.jet` set? If so, show `buf` in the window
• Otherwise, open a fresh window, resetting `self.win`.

Parameters: ~
• {buf} (`integer`)
• {opts} (`vim.api.keyset.win_config?`)
• {focus} (`boolean?`)

Return: ~
(`integer`)

Win:close({buf}) *jet-types-Win:close()*

Parameters: ~
• {buf} (`integer?`)

Win:toggle({buf}) *jet-types-Win:toggle()*

Parameters: ~
• {buf} (`integer`)

Win:get_curr_jet_buf({buf}) *jet-types-Win:get_curr_jet_buf()*

Parameters: ~
• {buf} (`integer?`)

Return: ~
(`integer?`)

Win:winnr({buf}) *jet-types-Win:winnr()*

Parameters: ~
• {buf} (`integer?`)

Return: ~
(`integer?`)

Win:get_buf() *jet-types-Win:get_buf()*

Return: ~
(`integer?`)

vim:tw=78:sw=4:ts=4:ft=help:norl:et:
Loading
Loading