Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
390eb6d
chore: fresh rebrand and rework.
timothyckl Aug 1, 2026
aff97e8
feat(patch): add visual selection capture module
timothyckl Aug 1, 2026
c556517
feat(patch): capture selection context
timothyckl Aug 3, 2026
d8121be
refactor(patch): extract selection capture helpers
timothyckl Aug 3, 2026
cfd7f98
refactor(patch): remove dead selection code
timothyckl Aug 3, 2026
4da98db
feat(patch): request inline edits from Pi
timothyckl Aug 4, 2026
4c9728b
feat(patch): support cancelling Pi requests
timothyckl Aug 4, 2026
8b32f0e
refactor(patch): encapsulate active request state
timothyckl Aug 5, 2026
c72880d
feat(patch): prompt for patch instructions
timothyckl Aug 5, 2026
5b3d408
chore(patch): remove unused ui scaffolding
timothyckl Aug 5, 2026
e49e161
refactor(patch): preserve selection metadata
timothyckl Aug 6, 2026
6fa0071
feat(patch): apply generated replacement
timothyckl Aug 6, 2026
6aa3327
feat(patch): preview generated replacement
timothyckl Aug 9, 2026
c737f31
feat(patch): add replacement proposal actions
timothyckl Aug 10, 2026
19831a4
fix(patch): clean up accepted and rejected replacements
timothyckl Aug 10, 2026
143c2f9
fix(patch): handle failed replacement requests
timothyckl Aug 10, 2026
55c6b8e
feat(patch): add commands and configurable notifications
timothyckl Aug 13, 2026
50fbfcf
docs: add project to-do list
timothyckl Aug 13, 2026
e0a43c7
docs: track replacement notification task
timothyckl Aug 13, 2026
aa08497
feat(patch): show completion notification on patch success
timothyckl Aug 15, 2026
96c52e2
docs: mark completion notification as done
timothyckl Aug 15, 2026
79f372f
fix(patch): preserve undo when accepting replacements
timothyckl Aug 16, 2026
2467375
fix(patch): scope cancellation to active request
timothyckl Aug 16, 2026
14ecd26
fix(patch): hide preview while retrying
timothyckl Aug 16, 2026
ef0b1e5
feat: replace main implementation with v2 rewrite
timothyckl Aug 16, 2026
000029f
fix(input): reject blank instructions
timothyckl Aug 16, 2026
d681a82
feat: allow custom system prompts
timothyckl Aug 16, 2026
82d00c7
feat: add model selection
timothyckl Aug 17, 2026
dff2526
refactor: separate patch workflow responsibilities
timothyckl Aug 17, 2026
20f152c
fix: release proposals after source buffer deletion
timothyckl Aug 18, 2026
5dc5b4e
docs(selection): streamline API comments
timothyckl Aug 18, 2026
cdafd24
fix: preserve proposal when buffer update fails
timothyckl Aug 18, 2026
c369290
fix(rpc): retain final output and diagnostics
timothyckl Aug 18, 2026
98bb057
refactor: reduce workflow orchestration duplication
timothyckl Aug 18, 2026
16c8047
fix(rpc): surface settled provider errors
timothyckl Aug 18, 2026
9bf199b
test: add automated Neovim coverage
timothyckl Aug 19, 2026
144c837
fix(rpc): isolate inline generation environment
timothyckl Aug 20, 2026
7f88271
docs: clarify runtime requirements
timothyckl Aug 20, 2026
1d4aa46
test: run suite with mini.test in CI
timothyckl Aug 20, 2026
cf9f3db
docs: clarify installation and repository links
timothyckl Aug 20, 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
29 changes: 0 additions & 29 deletions .github/workflows/test.yml

This file was deleted.

48 changes: 48 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Tests

on:
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
test:
name: Neovim ${{ matrix.neovim }}
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
neovim: [v0.10.4, stable]

steps:
- name: Checkout repository
uses: actions/checkout@v7

- name: Install Neovim
uses: rhysd/action-setup-vim@v1
with:
neovim: true
version: ${{ matrix.neovim }}

- name: Checkout mini.test
uses: actions/checkout@v7
with:
repository: nvim-mini/mini.test
ref: 35c67cb7f2dc697ed0c7c06400fcf29a8a521d7d
path: .deps/mini.test

- name: Run tests
run: nvim --headless --noplugin -u tests/minimal_init.lua -c "lua MiniTest.run()"

- name: Compile runtime Lua
run: >-
nvim --headless -u NONE
-c "lua for _, f in ipairs(vim.fn.glob('lua/**/*.lua', false, true)) do assert(loadfile(f)) end; assert(loadfile('plugin/patch.lua'))"
-c qa
6 changes: 1 addition & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
cli/tau
cli/.*.bun-build
cli/node_modules/
.DS_Store
*.log

.deps/
node_modules/
49 changes: 10 additions & 39 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,17 @@
# Contributing to tau.nvim

Thanks for your interest in contributing!

## Getting Started

1. Fork the repo and clone your fork
2. Ensure you have [Neovim 0.10+](https://neovim.io) and [Bun](https://bun.sh) installed
3. Build the CLI: `cd cli && bun build --compile src/index.ts --outfile tau`

## Making Changes

1. Create a branch from `main`
2. Make your changes
3. Test manually in Neovim to verify behavior
4. Open a pull request against `main`

## Commit Conventions

This project follows [Conventional Commits](https://www.conventionalcommits.org/). Prefix your commit messages with a type:

- `feat:` — new feature
- `fix:` — bug fix
- `docs:` — documentation changes
- `chore:` — maintenance tasks (CI, dependencies, etc.)
- `refactor:` — code changes that neither fix a bug nor add a feature

Example: `feat: add support for streaming responses`
# Contributing

## Pull Requests

- Keep PRs focused — one feature or fix per PR
- Write a clear description of what changed and why
- Ensure existing functionality isn't broken

## Reporting Issues
1. Fork the repository.
2. Create a focused branch for your change.
3. Make your changes and add or update relevant tests.
4. Open a pull request targeting `main` and describe what changed.
5. Address any CI failures or review feedback.

Open an issue with:
## Tests

- Neovim version (`nvim --version`)
- Steps to reproduce
- Expected vs actual behavior
Tests run automatically on pull requests targeting `main`.

## License
## Issues and Questions

By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE).
For bug reports, issues, or questions, please [open an issue](https://github.com/timothyckl/patch.nvim/issues/new).
206 changes: 51 additions & 155 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,187 +1,83 @@
# tau.nvim
# patch.nvim

A minimal Neovim plugin for applying LLM edits to a visual selection or range.
A simple agentic inline code editor for Neovim.

Tau sends your instruction, selected code, nearby context, and optional context files to an OpenAI-compatible `/chat/completions` API, then streams the proposed edit back into Neovim for review.
Requires Neovim 0.10+, [nui.nvim](https://github.com/MunifTanjim/nui.nvim), and an authenticated [Pi](https://github.com/earendil-works/pi) installation.

<img width="4108" height="2392" alt="image" src="https://github.com/user-attachments/assets/83c013fc-b5d3-46bf-ad74-ea5b6f2f2632" />
## How it works

## Install
1. Enter Visual Line (`Shift + v`) mode and highlight some text or empty space.
2. Send an instruction, such as “Refactor this function” or “Add type hints.”
3. Wait for the suggestion, then accept, reject, or retry it.

Requirements: Neovim 0.10+, [Bun](https://bun.sh), and an OpenAI-compatible API endpoint.
## Authentication

Using [lazy.nvim](https://github.com/folke/lazy.nvim):
Authenticate through the Pi coding agent before using patch.nvim. Start Pi, enter `/login` (or `/login <provider>`), and follow the prompts:

```lua
{
"timothyckl/tau.nvim",
lazy = false,
build = "cd cli && bun run build",
config = function()
require("tau").setup({
api_url = "https://api.openai.com/v1", -- Tau appends /chat/completions
api_key = vim.env.OPENAI_API_KEY,
model = "gpt-4o",
})
end,
keys = {
{ "<leader>t", ":Tau<CR>", mode = "v", desc = "Tau: edit selection" },
{ "<C-t>", ":TauContext<CR>", mode = "n", desc = "Tau: context files" },
},
}
```sh
pi
```

Using `vim.pack`:
Alternatively, authenticate an OAuth provider from the command line:

`tau.nvim` requires [Bun](https://bun.sh) to build the CLI binary at `cli/tau`.
The plugin is not usable until that build has completed, because `:Tau`,
`:TauContext`, and `:TauCancel` all rely on the compiled CLI being present.

Minimal install:

```lua
vim.pack.add({
{
src = "https://github.com/timothyckl/tau.nvim"
},
})
```sh
cd ~/.pi/agent
npx @earendil-works/pi-ai login [provider]
```

Complete example with automatic rebuilds on install and update:
## Installation

```lua
-- Define this before vim.pack.add() so fresh installs also build cli/tau.
vim.api.nvim_create_autocmd("PackChanged", {
callback = function(ev)
local data = ev.data
if data.spec.name ~= "tau.nvim" then
return
end
if data.kind ~= "install" and data.kind ~= "update" then
return
end

local result = vim.system({ "bun", "run", "build" }, {
cwd = data.path .. "/cli",
text = true,
}):wait()

if result.code ~= 0 then
local output = vim.trim(
(result.stderr and result.stderr ~= "") and result.stderr or (result.stdout or "")
)
if output == "" then
output = ("bun run build exited with code %d"):format(result.code)
end
vim.notify(
("tau.nvim: failed to build cli/tau\n%s"):format(output),
vim.log.levels.ERROR
)
end
end,
})
[nvim-notify](https://github.com/rcarriga/nvim-notify) is optional. Omit it from the examples below and remove the `notify` option from the configuration to use `vim.notify` instead.

vim.pack.add({
{ src = "https://github.com/timothyckl/tau.nvim" },
})

require("tau").setup({
api_url = "https://api.openai.com/v1", -- Tau appends /chat/completions
api_key = vim.env.OPENAI_API_KEY,
model = "gpt-4o",
})

vim.keymap.set("v", "<leader>t", ":Tau<CR>", { desc = "Tau: edit selection" })
vim.keymap.set("n", "<C-t>", ":TauContext<CR>", { desc = "Tau: context files" })
vim.keymap.set("n", "<leader>T", ":TauCancel<CR>", { desc = "Tau: cancel request" })
```
### lazy.nvim

The `PackChanged` hook rebuilds `cli/tau` after every install and update, and
`vim.system(...):wait()` keeps the build synchronous so the CLI is present
before first use.

If you hit `ENOENT: no such file or directory ... /cli/tau`, the CLI was not
built yet. Rebuild it manually with:

```sh
cd <plugin>/cli && bun run build
```lua
{
"tim/patch.nvim",
dependencies = {
"MunifTanjim/nui.nvim",
"rcarriga/nvim-notify",
},
}
```

For Ollama-compatible local servers:
### packer.nvim

```lua
require("tau").setup({
api_url = "http://localhost:11434/v1",
api_key = "ollama",
model = "qwen2.5-coder:7b",
use({
"tim/patch.nvim",
requires = {
"MunifTanjim/nui.nvim",
"rcarriga/nvim-notify",
},
})
```

## Usage

Select code in visual mode, then run:
### vim-plug

```vim
:Tau rewrite this to be simpler
Plug 'MunifTanjim/nui.nvim'
Plug 'rcarriga/nvim-notify'
Plug 'tim/patch.nvim'
```

Or run `:Tau` without an instruction to open the instruction picker. While reviewing a streamed proposal:

| Key | Action |
| --- | --- |
| `<CR>` | Accept edit |
| `<Esc>` | Reject edit |
| `r` | Regenerate edit |

Tau automatically includes nearby lines from the active file. To add more files as context, press `<C-t>` in the instruction picker or run `:TauContext`.

In the context picker:

| Key / Symbol | Meaning |
| --- | --- |
| `<Space>` | Toggle file under cursor |
| `<Esc>` / `q` | Close picker |
| `◎` | Active file, always included |
| `●` | Selected context file |

Selected context files persist for the current Neovim session.

## Configuration

```lua
require("tau").setup({
api_url = "https://api.openai.com/v1",
api_key = vim.env.OPENAI_API_KEY,
model = "gpt-4o",

-- optional
context_lines = 30,
timeout_ms = 60000,
context_window = 128000,
temperature = 0.2,
max_tokens = 4096,
top_p = 1,
debug = false,
keys = { context = "<C-t>" },
require("patch").setup({
notify = require("notify"),
-- system_prompt = "Custom system instructions...",
-- model = "openai/gpt-5.6-sol",
})

vim.keymap.set("x", "<leader>p", "<Cmd>PatchCapture<CR>", { desc = "Capture selection and request a patch" })
vim.keymap.set("n", "<leader>pa", "<Cmd>PatchAccept<CR>", { desc = "Accept the active patch proposal" })
vim.keymap.set("n", "<leader>pr", "<Cmd>PatchReject<CR>", { desc = "Reject the active patch and restore the original" })
vim.keymap.set("n", "<leader>pR", "<Cmd>PatchRetry<CR>", { desc = "Generate a new active patch proposal" })
vim.keymap.set("n", "<leader>pc", "<Cmd>PatchCancel<CR>", { desc = "Cancel the active patch generation request" })
vim.keymap.set("n", "<leader>pm", "<Cmd>PatchMenu<CR>", { desc = "Select the model used for patch requests" })
```

| Option | Default | Description |
| --- | --- | --- |
| `api_url` | required | OpenAI-compatible API base URL |
| `api_key` | required | API key; prefer environment variables |
| `model` | `gpt-4o` | Model name |
| `context_lines` | `30` | Lines above and below the selection to include |
| `timeout_ms` | `60000` | Request timeout in milliseconds |
| `context_window` | unset | Model context window, used for token estimates |
| `temperature` | unset | Sampling temperature, `0` to `2` |
| `max_tokens` | unset | Maximum response tokens |
| `top_p` | unset | Nucleus sampling value, `0` to `1` |
| `debug` | `false` | Write diagnostics to `~/.local/state/tau/diag.log` |
| `keys.context` | `<C-t>` | Open context picker from the instruction picker |

Commands: `:Tau [instruction]`, `:TauCancel`, `:TauContext`.

## License

MIT
`system_prompt` is optional. When set, it completely replaces Patch's built-in system prompt.

`model` is optional. When omitted, Patch uses Pi's primary model. `PatchMenu` marks the effective model and allows selecting an override for the current Neovim session.
Loading