Read JSON Lines files (model eval logs, trajectory dumps, any .jsonl) as
humans, inside nvim — powered by a patched jless.
Opening a .jsonl gives you a collapsible tree where text fields read as
actual text: no quotes, no \n escapes, long paragraphs folded to your
window width, numeric lists compacted to one line. Records fold like jless
nodes. All your nvim bindings keep working — the buffer stays in normal mode
and only jless's keys are forwarded to it.
1 ▼ (11) {step: 0, question: […], side_input: "1 0 1 1 0", …}
2 step: 0
3 ▽ question: (9) ["A computer routine was developed to generate two …
4 A computer routine was developed to generate two numbers (x,y)
5 the first being a random number between 0 and 100 inclusive
6
7 A)(99,10)
8 B)(85,9)
.jsonl is one record per line — unreadable raw, and pretty-printing breaks
the one-record-per-line contract your tooling depends on. jless is the best
JSON tree viewer for the terminal, but it renders strings quoted and escaped,
which makes long model completions painful to read. This plugin preprocesses
records with jq (multiline/long strings → arrays of width-folded lines) and
views them in a lightly patched jless that renders string rows as bare text.
- nvim ≥ 0.10 (OSC 52 terminal passthrough),
jq - To build the patched jless:
cargo(rustup.rs) and on Linuxlibxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev - For clipboard over ssh: a terminal with OSC 52 support (ghostty, iTerm2,
kitty, wezterm, …) and
set -s set-clipboard onif inside tmux
lazy.nvim:
{
"wusche1/jless.nvim",
build = "./build.sh",
opts = {},
}Navigation is jless-native: j k h l move/collapse/expand, J K jump
siblings (records at top level), - focus parent, c e C E deep
collapse/expand, gg G, Ctrl-d/u, Enter toggles the node under the
cursor, / ? search (prompt opens in the cmdline), n N next/prev match,
q quit.
Yanks go to the system clipboard (over ssh too, via OSC 52) and resolve against the original file, not the folded view:
| Key | Copies |
|---|---|
yv |
raw value under the cursor (real newlines, original types) |
yp / yq |
jq path, e.g. .[7].question — paste into a jq command |
yb |
python-style path, e.g. [7]["question"] |
yk |
key |
ys |
the rendered line under the cursor |
yy |
the view-shaped value (folded lines), pretty-printed |
R re-renders at the current window width (after resizing a split) and
returns to the node you were on. i drops into raw terminal mode for
anything not forwarded; <Esc><Esc> comes back.
Everything else — window navigation, :bd, leader bindings, macros — is
plain nvim, because the buffer never enters terminal mode unless you ask.
:JlessView <file> opens any JSON/JSONL file this way regardless of the
pattern.
opts = {
pattern = "*.jsonl", -- files to take over (false: only :JlessView)
jless_bin = nil, -- default: bundled bin/jless, else `jless` on PATH
fold_margin = 12, -- columns reserved for indentation when folding
min_width = 40, -- never fold narrower than this
}patches/ holds one patch per feature (git format-patch style, each with a
rationale in its message) against the upstream commit pinned in
patches/UPSTREAM_COMMIT:
- string values render raw: no quote delimiters, no escaped
\n - string items in arrays drop their
[N]:index label (text blocks read as prose) - yank falls back to OSC 52 when no X11 clipboard is available (ssh/tmux)
yppaths include the top-level document index ([7].question), making them unambiguous for multi-record files and jq-scompatible- yanks are mirrored to
$JLESS_YANK_FILE, the side-channel this plugin uses to resolve original values and restore position across re-renders
jless by Paul Julius Martinez (MIT) does all the heavy lifting.
make unit # nvim -l tests/run.lua — the jq transform and R's path replay
make e2e # tests/e2e.sh — drives the real plugin in nvim inside tmux
make test # both
The unit tests need only nvim and jq. The e2e test additionally needs tmux
and a patched jless (make build); it opens the fixture, asserts on the
rendered screen, and exercises path-yanking, R, and q.
