Thanks for your interest in via! via is an experimental Rust terminal application that hosts Neovim and AI coding agents side-by-side in a single native window, using libghostty's VT engine for rendering and input. It emphasizes easy integration between agent and nvim while staying out of the way of the developer
Be kind, assume good intent, and keep discussions technical. We are a small project; constructive feedback is appreciated.
You will need:
- A recent stable Rust toolchain (the project uses Rust 2024 edition).
- Zig 0.15.2 — required to build the vendored
libghostty-vtterminal engine at compile time. - git — the
libghostty-vt-sysbuild script uses it to fetch Ghostty sources. - On Linux (the primary development and release target): system development libraries for fonts, Wayland/X11, and input.
sudo apt-get update
sudo apt-get install -y \
libfontconfig1-dev \
libfreetype6-dev \
libwayland-dev \
libx11-dev \
libx11-xcb-dev \
libxcb1-dev \
libxcb-render0-dev \
libxcb-shape0-dev \
libxcb-xfixes0-dev \
libxkbcommon-dev \
pkg-configThe project pins Rust 1.93 (MSRV), Zig, and rust-analyzer in mise.toml:
mise installThe mise.toml also defines handy tasks (run with mise <task> or
mise run <task>):
mise test
mise fmt
mise clippy
mise dev # foreground run (no detach)
mise bench
mise build
mise cleanList available tasks with mise tasks. These activate the pinned tools (Rust,
Zig, etc.) for you — so mise build uses the project toolchain even if your
default rustup toolchain is older.
Otherwise, install Rust ≥ 1.93 and Zig 0.15.2 manually and ensure they are on
PATH.
cargo build --release
./target/release/vialibghostty-vt is statically linked; no extra runtime .so search paths are
needed.
First builds are slow. The dependency
libghostty-vt = { git = "...", rev = "..." } (see Cargo.toml)
triggers a checkout of Ghostty sources + a Zig build of the VT core during
cargo build. Results are cached under target/.
For local iteration without detach (Linux detaches by default to free the terminal):
VIA_FOREGROUND=1 cargo run -- ...
# or, if using mise:
mise devWith mise (recommended if you're already using it for Zig):
mise test
mise fmt
mise clippyRaw cargo commands (always work):
cargo test # unit tests (co-located with sources under #[cfg(test)])
cargo fmt -- --check
cargo clippy -- -D warningsNeovim Lua tests (the :ViaTasks PM UI and other nvim/ integration):
scripts/test-nvim.sh # headless nvim; requires nvim >= 0.9 for `-l`
# or: mise run test-nvimThese live under nvim/tests/ and use a tiny built-in harness (no
busted/plenary). Pure helpers (parse_row, format_row, build_content) are
tested directly; board actions and the task-body float are tested with the
command runner (M.run) stubbed, so no real via binary or live session is needed.
CI runs this as the nvim lua tests job.
We use:
rustfmt.tomlat the root (edition + a few style choices).clippy.toml(currently minimal; MSRV will be recorded here later).
All tests live in src/** as #[cfg(test)] mod tests. There is no separate
tests/ crate today (the combination of native winit window + PTYs + Neovim
makes fully hermetic GUI integration tests expensive to maintain).
For a live-session orchestration smoke test, start via with an ACP-capable
primary agent such as opencode, then run this from a via-launched pane:
scripts/e2e-agent-orchestration.sh
# or: mise run e2e-agentsThe script is intentionally opt-in, not CI-gated. It drives real via agent
commands against the active VIA_SESSION: spawns reviewer and orchestrator,
checks ACP registry mode, verifies ACP prompt delivery, verifies PTY mailbox-only
send behavior, checks missing-recipient failure, and terminates spawned panes.
For a higher-level skills / harness eval (create a task board, delegate to
helpers, implement a tiny fixture until green), see
evals/task-board-loop/:
# After an agent run (portable: paste PROMPT.md into any harness):
EVAL_BOARD_ID=eval-<run-id> mise run eval-task-board-grade
# Or unattended inside a live via session (ACP spawn required):
mise run eval-task-board-viaAlso opt-in and not CI-gated. Grading is deterministic (grade.sh +
fixture/verify.sh); there is no LLM judge.
We have Criterion micro-benchmarks for the two most user-visible performance-sensitive surfaces:
- Layout calculations (
src/ui/ghostty/layout.rs): split decisions, column reservation, focus-after-reference. - Link / OSC 8 / symbol scanning (
src/ui/ghostty/links.rs):reference_target_from_rowand URI variants, used for Ctrl+click routing and Ctrl-held clickable cues in visible agent output.
If your change touches layout math or the reference scanner, run cargo bench
locally (before/after) and include a short summary or target/criterion/ report
link in the PR description. The CI runs a quick, non-blocking informational
cargo bench pass on every PR for visibility (see .github/workflows/ci.yml);
it uses reduced sample counts so it finishes in reasonable time. For serious
measurements use the defaults on your machine.
cargo bench (or mise bench) requires the same system libs + Zig as a normal
build.
via launches Neovim in one pane and your agent in another (or a single-pane ACP layout).
ACP agents (spawned orchestration / helpers):
VIA_FOREGROUND=1 cargo run -- --agent opencode
via agent spawn --id orchestrator --role orchestratorNormal mode / PTY agents (primary interactive pane; context injected via PTY):
VIA_FOREGROUND=1 cargo run -- --agent claudeIn Neovim:
<leader>abor:ViaBufferSend— explicitly send the current buffer (or visual selection) to the primary PTYagentpane. For ACP helpers, usevia agent send --to <id>or Luarequire('via').agent.send.via session refresh [--file PATH]— ask Neovim to reload externally changed buffers after agent edits. via also runs:checktimeonFocusGainedandBufEnter.- Hold Ctrl in the agent output to highlight clickable filenames, symbols, and
OSC 8 hyperlinks. Ctrl-click file paths or
symbol://Foo::barreferences to open them in the Neovim pane; Ctrl-click external OSC 8 hyperlinks to open them in the system browser.
See the main README.md for configuration (~/.config/via/via.conf,
env vars, --agent-pane-cols, review backends, font tweaks, etc.) and the
embedded Neovim Lua bridges (nvim/*.lua).
- Original lightweight design brief: project.md
- ACP investigation and status: acp.md
- Core runtime pieces (read these for context before touching):
src/mediator.rs— Tokio-based event router. Handles Neovim RPC events, ACP client, editor state (src/editor.rs), LSP bridge, PTY output, and UI commands.src/ui/ghostty.rs(andsrc/ui/ghostty/*) — winit event loop + libghostty-vt surfaces, pane layout/splits/fullscreen, input routing, OSC 8 link extraction, Ratatui-backed ACP pane, review terminal toggling (hunk or nvim), font rendering with cosmic-text.src/acp.rs— small ACP JSON-RPC client (initialize, new_session, prompt, tool results).src/nvim.rs+nvim/— nvim-rs RPC client + embedded Lua templates for file/symbol open, diagnostics export, review.src/config.rs,src/session.rs,src/cli/— layered config (CLI > env > via.conf > defaults), session manifests (for cross-processvia session *commands), subcommand implementations.src/pty.rs— portable-pty wrapper with coalesced output notification.src/lsp_bridge.rs— Unix socket bridge allowing the agent (via the skill) to perform LSP requests in the live Neovim session.
Key invariants the tests and benches protect:
- Layout must always reserve at least the editor minimum columns; agent pane respects its min/max range.
- Clicking references (file + optional
:line, or symbol URIs) must be fast and focus the editor pane appropriately. - Explicit context for the primary pane is
:ViaBufferSend; ACP helpers use targetedvia agent send --to <id>/ Luaagent.send.
- Pane layout and resize edge cases (
src/ui/ghostty/layout.rsand its tests). - Link/symbol scanning robustness and performance (
src/ui/ghostty/links.rs). - Completing the ACP TUI UX (model/mode selection, tool call rendering, diff
preview, styling) — see
src/acp_tui/,src/ui/ghostty/acp_modal.rs, andmediator.rs. - Review backend improvements or the "hunk" backend.
- Diagnostics / session CLI ergonomics and the Lua side (
nvim/diagnostics.lua,src/cli/session.rs). - Making the first-time build experience better (docs, caching, clearer errors from the ghostty build script).
- More tests, especially property-style or boundary cases for layout and link parsing; Criterion benchmarks for the two hot paths (layout math and per-row reference scanning).
- Documentation, examples, and contributor onboarding.
If you're touching layout math or the main reference scanner, please add or update a benchmark.
The mock_acp_agent cargo example is a controllable fake ACP agent for
exercising the permission modal queue UX (pending badge, Tab/Shift+Tab
navigation, FIFO drain, terminate-with-pending) without a real backend.
Build the runnable example binary (the test target has a hashed filename):
cargo build --example mock_acp_agentSpawn it as an ACP pane (use an absolute path; the trailing acp token is
required so via classifies the pane as ACP, not PTY):
via agent spawn --id mock1 --command "$(pwd)/target/debug/examples/mock_acp_agent acp"After handshake, the mock prints an interactive menu in the ACP pane transcript.
Type commands directly in the ACP TUI pane, or send the same text with
via agent send --to mock1 -m '<command>' (both arrive as session/prompt once
the spawn command is ACP-classified).
Commands:
1orburst [N] [delay_ms]— fire N permission modals concurrently; the prompt stays pending until all complete.2ordrip [N] [delay_ms]— one modal at a time; the next starts only after the previous fake command finishes (reproduces a modal arriving while another is on screen).help— show the menu again.
Omitted N and delay_ms use clap defaults (--requests, default 3;
--delay-ms, default 0). Unknown or malformed input prints a concise
help/error transcript and ends the turn without firing requests.
Each fake command emits a full tool-call lifecycle: tool_call pending →
session/request_permission → (after the user answers) in_progress →
completed or a terminal failure — nothing is executed on the host.
Fake commands use demo-command-<n> --not-auto-approved in tool metadata so
via's auto-approve policy does not skip the modal. Each resolution is echoed as
an agent_message_chunk transcript line
(mock: request <toolCallId> resolved -> <optionId|cancelled>). Tool-call and
message IDs increase monotonically across repeated prompts; a second drip is
rejected while one is active. session/cancel cancels in-flight fake tool calls
and completes the prompt with stopReason=cancelled.
UX checklist when testing manually:
burstorburst 3: modal shows(+2 pending)badge; Tab/Shift+Tab cycles without answering; answering drains FIFO and echoes resolve lines; prompt stays pending until all N complete.drip 3: first modal visible; after answering (and fake tool completion), the next appears (exercises queue push while UI is active).- Retrigger
burstafter a burst: new modals get fresh monotonic tool-call IDs. - Start
dripwhile a drip is active: transcript shows rejection, no new modals. - Cancel (
session/cancel/ pane cancel): in-flight fake tool calls terminalize; prompt completes withstopReason=cancelled. - Terminate the pane with pending modals: queue clears without hanging.
-
Fork the repo and create a topic branch.
-
Make your change + tests.
-
Run locally:
cargo fmt -- --check cargo clippy -- -D warnings cargo test(If adding layout/links changes: also
cargo bench/mise benchand note before/after numbers in the PR.) -
Push and open a PR against
main. -
In the PR description, explain the motivation, user impact, and any performance or behavior changes. Link related issues.
CI (once added) will enforce formatting, clippy, and tests on Linux. The release workflow remains separate (triggered by GitHub releases).
Small, reviewable PRs are strongly preferred. We can always iterate.
- Create a GitHub release with a tag like
v0.3.0. - The existing
.github/workflows/release.ymlbuilds onubuntu-24.04, packages the binary + README into avia-<tag>-linux-x86_64.tgz(plus SHA256), and uploads the assets. - Currently only linux-x86_64 is produced. Cross-platform binaries or source builds for macOS/Windows are future work.
Open a GitHub issue or discussion. Thank you for helping make via better!