Rewrite a visual selection with the OpenAI API. The :Rewrite command sends the selected text and the words after the command to POST /chat/completions, and replaces the selection in place with the reply.
Warning: Large Language Model-Generated Code
This code functions as of the time of writing, yet it has received little to no human review. It must not be regarded as reliable: bugs and stability issues are to be expected.
- Neovim 0.11 or later, for
vim.fn.getregionpos(). curlonPATH, built with TLS.- An API key in
OPENAI_API_KEY.
Append the directory to runtimepath:
vim.opt.runtimepath:append(vim.fn.expand("~/plugins/vbcdd-rewriter"))Select text with v for characters or V for whole lines, then run :Rewrite with the instruction:
:'<,'>Rewrite paraphrase thisVisual mode fills in the '<,'> range; the instruction holds everything after Rewrite. The reply replaces the selection once the API answers, and the buffer stays untouched until then.
setup() merges its argument into the defaults. Call it at startup, before the first :Rewrite.
require("rewriter").setup({
api_key = vim.env.OPENAI_API_KEY,
model = "gpt-4o-mini",
base_url = "https://api.openai.com/v1",
reasoning_effort = "low",
extra_headers = { ["X-Title"] = "vbcdd-rewriter" },
file_context = true,
timeout_ms = 120000,
max_prompt_bytes = 100 * 1024,
})| Option | Default | Meaning |
|---|---|---|
curl_cmd |
vim.fn.exepath("curl") |
Executable that serves one request. |
api_key |
vim.env.OPENAI_API_KEY |
Bearer token. |
model |
gpt-4o-mini |
Model that answers the request. |
base_url |
https://api.openai.com/v1 |
API root; another host serves a compatible API. |
reasoning_effort |
"" |
Thinking level, sent as reasoning_effort; empty omits the field. |
extra_headers |
{} |
Request headers, as name = value. |
file_context |
false |
Send the whole buffer ahead of the selection. |
timeout_ms |
120000 |
Time limit for one request, in milliseconds. |
max_prompt_bytes |
100 * 1024 |
Cap on one request: the context, the instruction, and the selection. |
system_prompt |
the plugin's own | Replaces the default system message. |
- One request runs per buffer. A second
:Rewritebefore the reply reports the request in flight. - An edit during a request leaves the buffer alone and moves the reply to the
"register. - Charwise (
v) and linewise (V) selections work. A blockwise selection (<C-v>) reports an error. - A prompt over
max_prompt_bytesreports the size and the limit. - The request travels on
curl's stdin through--config -, so the key stays off the process list. curlruns with--silent --show-error: the reply lands on stdout, and a transport failure on stderr.- The request holds two messages: the system prompt, then one user message with the instruction and the selection. The plugin reads the reply from the first choice.
file_contextputs the whole buffer into that user message first, inside<file path="…">tags, and adds a sentence to the system message that names those tags as context. The model then reads the selection in place.max_prompt_bytescounts the context, the instruction, and the selection together.- One request carries one instruction. The plugin keeps no conversation: the model sees no earlier instruction, selection, or reply, and the buffer is the only place a reply lands.
reasoning_efforttravels verbatim, so a model that lacks reasoning keeps its field only when the setting is non-empty. Values come from the API:minimal,low,medium,high.- An extra header whose name holds whitespace or a colon, or whose value holds a control character, reports an error instead of reaching
curl.
vale sync # once, fetches the prose lints
nvim --headless -u NONE -l tests/run.lua # unit tests, no network
REWRITER_E2E=1 nvim --headless -u NONE -l tests/e2e.lua # one live rewrite
scripts/check # formatters and prose lintsReleased into the public domain under the Unlicense.