neovim-wanderland
Neovim Wanderland Integration
Edit Oculus nodes and execute fences directly from Neovim.
Overview
The wanderland.lua plugin provides Telescope-based integration with:
- Oculus - Graph-based knowledge system (port 7778)
- Stuffy - Live markdown streaming/preview (port 8080)
Installation
Canonical Source:
~/.config/nvim/lua/wanderland.luaThe source file contains@oculusannotations pointing back to this documentation.
1. Add to Neovim Config
Copy wanderland.lua to ~/.config/nvim/lua/:
# The file location
~/.config/nvim/lua/wanderland.lua2. Load in init.lua
-- In ~/.config/nvim/init.lua
local wanderland = require("wanderland")
wanderland.setup()3. Register Keybindings (Which-Key)
local wk = require("which-key")
wk.add({
-- Wanderland (SPC W)
{ "<leader>W", group = "wanderland" },
-- Stuffy (SPC W s)
{ "<leader>Ws", group = "stuffy" },
{ "<leader>Wsu", wanderland.stuffy_upload_buffer, desc = "Upload buffer" },
{ "<leader>Wsd", wanderland.stuffy_download_to_buffer, desc = "Download channel" },
{ "<leader>Wsc", function() -- channel picker end, desc = "Set channel" },
{ "<leader>Wsb", wanderland.stuffy_open_browser, desc = "Open in browser" },
{ "<leader>Wss", wanderland.stuffy_toggle_auto_sync, desc = "Toggle auto-sync" },
-- Oculus (SPC W o)
{ "<leader>Wo", group = "oculus" },
{ "<leader>Wop", wanderland.oculus_download_node, desc = "Pull node (raw)" },
{ "<leader>Wov", wanderland.oculus_download_node_rendered, desc = "View node (rendered)" },
{ "<leader>WoP", wanderland.oculus_publish, desc = "Publish node" },
{ "<leader>Woe", wanderland.oculus_show_edges, desc = "Navigate edges" },
{ "<leader>Wob", wanderland.oculus_open_browser, desc = "Open in browser" },
{ "<leader>Wow", wanderland.oculus_edit_with_preview, desc = "Edit with preview" },
{ "<leader>Wof", wanderland.oculus_browse_fences, desc = "Browse fences" },
{ "<leader>Wor", wanderland.oculus_run_fence, desc = "Run fence" },
{ "<leader>WoR", wanderland.oculus_run_fence_results, desc = "Run fence (results)" },
{ "<leader>Wok", wanderland.oculus_poke_quick, desc = "Quick poke" },
})Keybindings Reference
Stuffy (SPC W s)
| Key | Action | Description |
|---|---|---|
SPC W s u |
Upload buffer | Push current buffer to Stuffy channel |
SPC W s d |
Download channel | Pick channel and load to new buffer |
SPC W s c |
Set channel | Choose active Stuffy channel |
SPC W s b |
Open browser | View current channel in browser |
SPC W s s |
Toggle auto-sync | Auto-upload on save |
Oculus (SPC W o)
| Key | Action | Description |
|---|---|---|
SPC W o p |
Pull node (raw) | Download node source for editing |
SPC W o v |
View rendered | Download executed/rendered view |
SPC W o P |
Publish node | Save buffer back to Oculus |
SPC W o e |
Navigate edges | Telescope picker for N/S/E/W links |
SPC W o b |
Open browser | View node in Stuffy browser |
SPC W o w |
Edit with preview | Open raw + browser + auto-sync |
SPC W o f |
Browse fences | Telescope fence picker |
SPC W o r |
Run fence | Prompt for ID, execute, notify |
SPC W o R |
Run fence (results) | Prompt for ID, execute → split pane |
SPC W o k |
Quick poke | Poke value at path (shows current) |
Fence Picker (SPC W o f)
In the fence picker Telescope window:
| Key | Action |
|---|---|
Enter |
Go to fence location in node |
Ctrl-x |
Execute fence (prompts for params if needed) |
Ctrl-p |
Execute with params (always prompts) |
Workflows
1. Quick Node Edit
SPC W o p → Pick node
(edit)
SPC W o P → Publish changes2. Live Preview Editing
SPC W o w → Pick node (opens browser + enables auto-sync)
(edit & save → browser updates automatically)
SPC W o P → Publish when done3. Execute Labeled Fence
SPC W o f → Open fence picker
/magic-8 → Filter to magic-8-ball
Ctrl-p → Execute with params
{"question": "Should I deploy?"} → Enter JSON4. Navigate Graph
SPC W o p → Open a node
SPC W o e → Show edges (N/S/E/W)
(select) → Navigate to linked node5. Client-Side Composition
Build custom views by requesting specific fences at different levels:
-- Lua example: selective fence execution
local result = wanderland.oculus_compose_sparse("jenkins-job-config", {
["job-list"] = "rendered", -- Execute: get live Jenkins data
["config"] = "stalk", -- Don't execute: just show YAML
})
-- NeoVim can now stitch these together:
-- - Show config fence as editable code block
-- - Show job-list fence as live data tableThis enables "living documents" where some fences show live data while others remain editable source.
Configuration
wanderland.setup({
oculus_url = "http://localhost:7778",
stuffy_url = "http://localhost:8080",
browser = "firefox", -- or "open" on macOS, "xdg-open" on Linux
})API Functions
All functions are available on the wanderland module:
Stuffy
wanderland.stuffy_upload(channel, content, filename)
wanderland.stuffy_download(channel) -- returns content string
wanderland.stuffy_channels() -- returns list of channel names
wanderland.stuffy_set_channel(name)
wanderland.stuffy_upload_buffer()
wanderland.stuffy_download_to_buffer()
wanderland.stuffy_open_browser()
wanderland.stuffy_toggle_auto_sync()Oculus
Oculus
| Function | Description |
|---|---|
oculus_list_nodes() |
Get all node slugs |
oculus_get_node(slug, mode) |
Get node content (raw/rendered) |
oculus_download_node(mode) |
Telescope picker → buffer |
oculus_show_edges() |
Navigate via directional links |
oculus_publish() |
Save buffer to Oculus |
oculus_refresh_node() |
Reload raw from server |
oculus_rerender_node() |
Reload rendered from server |
Peek/Poke
Read and write values at paths. Values are parsed as JSON first (for booleans, numbers, arrays, dicts), falling back to string.
| Function | Description |
|---|---|
oculus_peek(slug, path, opts) |
Get value at path |
oculus_peek_interactive() |
Prompt for slug, path → result split |
oculus_poke(slug, path, value, opts) |
Write value at path |
oculus_poke_interactive() |
Prompt for slug, path, value |
oculus_poke_quick() |
Current node: path, value (shows current) |
Value Parsing Examples:
true/false→ boolean42/3.14→ number[1, 2, 3]→ array{"key": "val"}→ dicthello world→ string (not valid JSON)
Fences
wanderland.oculus_list_fences(type_filter) -- returns list of fences
wanderland.oculus_execute_fence(fence_id, params)
wanderland.oculus_browse_fences()
wanderland.oculus_run_node(slug)Compose API (Sparse Mode)
Request specific fences at different render levels - the client controls composition:
-- Sparse compose: request only the fences you need
wanderland.oculus_compose_sparse(slug, {
["magic-8-ball"] = "rendered", -- Execute this one (by label)
["config"] = "stalk", -- Raw content only (by label)
["3"] = "rendered", -- By index
})
-- Returns:
-- {
-- sparse = true,
-- fences = {
-- ["magic-8-ball"] = { executed = true, data = {...} },
-- ["config"] = { executed = false, content = "..." },
-- }
-- }Endpoint: POST /api/oculus/nodes/{slug}/compose
{
"fences": {
"fence-label": "rendered",
"0": "stalk"
}
}Addressing modes:
- By label:
"magic-8-ball"→ resolves via fence index - By index:
"0","3"→ direct fence position
Levels:
| Level | Alias | Result |
|---|---|---|
seed |
raw |
Raw markdown |
sprout |
substituted |
Variables resolved |
stalk |
interpolated |
Fences visible, not executed |
clouds |
rendered |
Fences executed |
Runbook safety: Fences with execute=false never execute via compose, even at rendered level.
State Variables
wanderland.current_channel -- Active Stuffy channel
wanderland.current_node -- Currently loaded Oculus node slug
wanderland.auto_sync -- Auto-sync enabled?
vim.b.oculus_mode -- Buffer-local: "raw" or "rendered"Safety Features
- Publish protection: Cannot publish
renderedbuffers (would overwrite source with executed content) - Mode tracking: Buffer names show
[raw]or[rendered]to indicate mode - Result reuse: Fence results open in named buffers that get reused
Dependencies
- telescope.nvim
- curl (for API calls)
- jq (optional, for pretty-printing JSON results)
See Also
- cmd-fence - Fence system reference
- fence-params - Parameter documentation API
- how-to-create-oculus-tool - Creating fence tools
Markdown Editing
Since we do a lot of markdown editing with Oculus nodes, here's the recommended setup.
Plugins
-- Table editing (Tab/Shift-Tab to navigate, auto-reflow)
{ "SCJangra/table-nvim", ft = "markdown", ... }
-- Editing tools (links, lists, heading navigation)
{ "tadmccorkle/markdown.nvim", ft = "markdown", ... }
-- Subtle rendering (headings, bullets - raw in insert mode)
{ "MeanderingProgrammer/render-markdown.nvim", ft = "markdown", ... }Virtual Buffer Fix
Oculus buffers are virtual (buftype=nofile) and don't match *.md patterns. Add these autocmds to make table-nvim work:
-- Keymaps for virtual markdown buffers
vim.api.nvim_create_autocmd("FileType", {
pattern = "markdown",
callback = function(opts)
local ok, maps = pcall(require, "table-nvim.keymaps")
if ok then maps.set_keymaps(opts.buf) end
end,
})
-- Reflow tables on InsertLeave for virtual buffers
vim.api.nvim_create_autocmd("InsertLeave", {
callback = function()
if vim.bo.filetype ~= "markdown" then return end
local ok, utils = pcall(require, "table-nvim.utils")
if not ok then return end
local MdTable = require("table-nvim.md_table")
local root = utils.get_tbl_root(vim.treesitter.get_node())
if root then MdTable:new(root):render() end
end,
})Keybindings Summary
| Key | Action | Plugin |
|---|---|---|
Tab |
Next table cell | table-nvim |
S-Tab |
Prev table cell | table-nvim |
Alt-j |
Insert row below | table-nvim |
Alt-k |
Insert row above | table-nvim |
]] |
Next heading | markdown.nvim |
[[ |
Prev heading | markdown.nvim |
[p |
Parent heading | markdown.nvim |
gx |
Follow link | markdown.nvim |
Alternatives Researched
| Plugin | Style | Notes |
|---|---|---|
| table-nvim | Auto-format as you type | Tab navigation, Lua |
| markdown-table-mode.nvim | Format on insert leave | Lightweight, :Mtm toggle |
| vim-table-mode | Manual reflow | Classic, <leader>tr to realign |
Related Cases
- [[reactive-markdown-tables]] - Oculus virtual fence for programmatic table manipulation (A1 cell addressing, formulas)
Slots
North
slots:
- context:
- Linking client registry to neovim implementation docs
slug: wanderland-clientsSouth
slots:
- context:
- Case implements Living Runbooks feature documented in parent
slug: neovim-living-runbooks
- context:
- Tutorial child of main docs
slug: neovim-living-runbooks-tutorialWest
slots:
- context:
- Editor integration for fence params
slug: fence-params
- context:
- Linking neovim guide to fence params docs
slug: fence-paramsEast
slots:
- context:
- Linking sibling editor integrations - both implement SPC W keybindings for Wanderland
slug: emacs-integration