A Neovim plugin that provides local edit completions and cursor predictions.
[!NOTE]
Help improve completions by contributing anonymous usage data to our open dataset. Set
contribute_data = truein your config to opt in. No code content or file paths are collected — see the schema.
[!WARNING]
This is an early-stage project. Expect bugs, incomplete features, and breaking changes. Make sure to regularly update the plugin.
{
"cursortab/cursortab.nvim",
-- version = "*", -- Use latest tagged version for more stability
lazy = false, -- The server is already lazy loaded
build = "cd server && go build",
config = function()
require("cursortab").setup()
end,
}
use {
"cursortab/cursortab.nvim",
-- tag = "*", -- Use latest tagged version for more stability
run = "cd server && go build",
config = function()
require("cursortab").setup()
end
}
The fastest way to get started is with the Mercury API provider (hosted, no local GPU needed):
Get an API key from Inception Labs
Set the environment variable:
export MERCURY_AI_TOKEN="your-api-key-here"
Add the provider to your setup:
require("cursortab").setup({
provider = {
type = "mercuryapi",
api_key_env = "MERCURY_AI_TOKEN",
},
})
If you prefer local inference, use the sweep provider with
llama.cpp:
llama-server -hf sweepai/sweep-next-edit-1.5b --port 8000
require("cursortab").setup({
provider = {
type = "sweep",
url = "http://localhost:8000",
},
})
See Providers for all available options.
require("cursortab").setup({
enabled = true,
log_level = "info", -- "trace", "debug", "info", "warn", "error"
state_dir = vim.fn.stdpath("state") .. "/cursortab", -- Directory for runtime files (log, socket, pid)
contribute_data = false, -- Opt-in: send anonymous metrics to train a better gating model
keymaps = {
accept = "<Tab>", -- Keymap to accept completion, or false to disable
partial_accept = "<S-Tab>", -- Keymap to partially accept, or false to disable
trigger = false, -- Keymap to manually trigger completion, or false to disable
},
ui = {
completions = {
addition_style = "dimmed", -- "dimmed" or "highlight"
fg_opacity = 0.6, -- opacity for completion overlays (0=invisible, 1=fully visible)
},
jump = {
symbol = "", -- Symbol shown for jump points
text = " TAB ", -- Text displayed after jump symbol
show_distance = true, -- Show line distance for off-screen jumps
},
},
behavior = {
idle_completion_delay = 50, -- Delay in ms after idle to trigger completion (-1 to disable)
text_change_debounce = 50, -- Debounce in ms after text change to trigger completion (-1 to disable)
max_visible_lines = 12, -- Max visible lines per completion (0 to disable)
enabled_modes = { "insert", "normal" }, -- Modes where completions are active
cursor_prediction = {
enabled = true, -- Show jump indicators after completions
auto_advance = true, -- When no changes, show cursor jump to last line
proximity_threshold = 2, -- Min lines apart to show cursor jump (0 to disable)
},
ignore_paths = { -- Glob patterns for files to skip completions
"*.min.js",
"*.min.css",
"*.map",
"*-lock.json",
"*.lock",
"*.sum",
"*.csv",
"*.tsv",
"*.parquet",
"*.zip",
"*.tar",
"*.gz",
"*.pem",
"*.key",
".env",
".env.*",
"*.log",
},
ignore_filetypes = { "", "terminal" }, -- Filetypes to skip completions
ignore_gitignored = true, -- Skip files matched by .gitignore
},
provider = {
type = "inline", -- Provider: "inline", "fim", "sweep", "sweepapi", "zeta", "copilot", or "mercuryapi"
url = "http://localhost:8000", -- URL of the provider server
api_key_env = "", -- Env var name for API key (e.g., "OPENAI_API_KEY")
model = "", -- Model name
temperature = 0.0, -- Sampling temperature
max_tokens = 512, -- Max tokens to generate
top_k = 50, -- Top-k sampling
completion_timeout = 5000, -- Timeout in ms for completion requests
max_diff_history_tokens = 512, -- Max tokens for diff history (0 = no limit)
completion_path = "/v1/completions", -- API endpoint path
fim_tokens = { -- FIM tokens (for FIM provider)
prefix = "<|fim_prefix|>",
suffix = "<|fim_suffix|>",
middle = "<|fim_middle|>",
},
privacy_mode = true, -- Don't send telemetry to provider
},
blink = {
enabled = false, -- Enable blink source
ghost_text = true, -- Show native ghost text alongside blink menu
},
debug = {
immediate_shutdown = false, -- Shutdown daemon immediately when no clients
},
})
For detailed configuration documentation, see :help cursortab-config.
The plugin defines the following highlight groups with default = true, so you
can override them in your colorscheme or config:
| Group | Default | Purpose |
|---|---|---|
CursorTabDeletion |
bg = "#4f2f2f" |
Background for deleted text |
CursorTabAddition |
bg = "#394f2f" |
Background for added text |
CursorTabModification |
bg = "#282e38" |
Background for modified text |
CursorTabCompletion |
fg = "#80899c" |
Foreground for completion text |
CursorTabJumpSymbol |
fg = "#373b45" |
Jump indicator symbol |
CursorTabJumpText |
bg = "#373b45", fg = "#bac1d1" |
Jump indicator text |
To customize, set the highlight before or after calling setup():
vim.api.nvim_set_hl(0, "CursorTabAddition", { bg = "#1a3a1a" })
The plugin supports seven AI provider backends: Inline, FIM, Sweep, Sweep API, Zeta, Copilot, and Mercury API.
| Provider | Hosted | Multi-line | Multi-edit | Cursor Prediction | Streaming | Model |
|---|---|---|---|---|---|---|
inline |
Any base model | |||||
fim |
✓ | ✓ | Any FIM-capable | |||
sweep |
✓ | ✓ | ✓ | ✓ | Sweep Next-Edit family | |
sweepapi |
✓ | ✓ | ✓ | ✓ | ✓ | sweep-next-edit-7b |
zeta |
✓ | ✓ | ✓ | ✓ | zeta |
|
copilot |
✓ | ✓ | ✓ | ✓ | GitHub Copilot | |
mercuryapi |
✓ | ✓ | ✓ | ✓ | mercury-edit |
Context Per Provider:
| Context | inline | fim | sweep | zeta | sweepapi | copilot | mercuryapi |
|---|---|---|---|---|---|---|---|
| Buffer content | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Edit history | ✓ | ✓ | ✓ | ✓ | |||
| Previous file state | ✓ | ✓ | |||||
| LSP diagnostics | ✓ | ✓ | ✓ | ||||
| Treesitter context | ✓ | ✓ | ✓ | ✓ | |||
| Git diff context | ✓ | ✓ | ✓ | ✓ | |||
| Recent files | ✓ | ✓ | |||||
| User actions | ✓ |
End-of-line completion using OpenAI-compatible API endpoints. Works with any
OpenAI-compatible /v1/completions endpoint.
Requirements:
Example Configuration:
require("cursortab").setup({
provider = {
type = "inline",
url = "http://localhost:8000",
},
})
Example Setup:
# Using llama.cpp
llama-server -hf ggml-org/Qwen2.5-Coder-1.5B-Q8_0-GGUF --port 8000
Fill-in-the-Middle completion using standard FIM tokens. Uses both prefix
(before cursor) and suffix (after cursor) context. Compatible with Qwen,
DeepSeek-Coder, and similar models. Works with any OpenAI-compatible
/v1/completions endpoint.
Requirements:
Example Configuration:
require("cursortab").setup({
provider = {
type = "fim",
url = "http://localhost:8000",
},
})
Example Setup:
# Using llama.cpp with Qwen2.5-Coder 1.5B
llama-server -hf ggml-org/Qwen2.5-Coder-1.5B-Q8_0-GGUF --port 8000
# Or with Qwen 2.5 Coder 14B + 0.5B draft for speculative decoding
llama-server \
-hf ggml-org/Qwen2.5-Coder-14B-Q8_0-GGUF:q8_0 \
-hfd ggml-org/Qwen2.5-Coder-0.5B-Q8_0-GGUF:q8_0 \
--port 8012 \
-b 1024 \
-ub 1024 \
--cache-reuse 256
Sweep Next-Edit models for fast, accurate next-edit predictions. Sends full file for small files, trimmed around cursor for large files.
Available Models:
sweep-next-edit-v2-7B
— 8B params, highest qualitysweep-next-edit-1.5B
— 1B params, fast and accuratesweep-next-edit-0.5B
— 0.5B params, lightweightRequirements:
Example Configuration:
require("cursortab").setup({
provider = {
type = "sweep",
url = "http://localhost:8000",
},
})
Example Setup:
# Using llama.cpp
llama-server -hf sweepai/sweep-next-edit-1.5b --port 8000
# Or with a local GGUF file
llama-server -m sweep-next-edit-1.5b.q8_0.v2.gguf --port 8000
# With caching and speculative decoding
llama-server \
-m sweep-next-edit-1.5b.q8_0.v2.gguf \
--port 8000 \
--cache-reuse 64 \
--spec-type ngram-mod \
--spec-ngram-size-n 24 \
--draft-min 12 \
--draft-max 64
Sweep's hosted API for Next-Edit predictions. No local model setup required.
Requirements:
SWEEPAPI_TOKEN environment variable with your tokenExample Configuration:
# In your shell config (.bashrc, .zshrc, etc.)
export SWEEPAPI_TOKEN="your-api-token-here"
require("cursortab").setup({
provider = {
type = "sweepapi",
api_key_env = "SWEEPAPI_TOKEN",
},
})
Zed's Zeta model - a Qwen2.5-Coder-7B fine-tuned for edit prediction using DPO and SFT.
Requirements:
Example Configuration:
require("cursortab").setup({
provider = {
type = "zeta",
url = "http://localhost:8000",
model = "zeta",
},
})
Example Setup:
# Using vLLM
vllm serve zed-industries/zeta --served-model-name zeta --port 8000
# See the HuggingFace page for optimized deployment options
GitHub Copilot completions using the official
copilot-language-server
LSP server, enabled with vim.lsp.enable. Can be installed in multiple ways:
npm or your OS's package manager:LspCopilotSignInRequirements:
copilot-language-server installed and enabled via vim.lsp.enableExample Configuration:
require("cursortab").setup({
provider = {
type = "copilot",
},
})
Mercury is Inception Labs' hosted Next-Edit prediction model.
Requirements:
MERCURY_AI_TOKEN environment variable)Example Configuration:
require("cursortab").setup({
provider = {
type = "mercuryapi",
api_key_env = "MERCURY_AI_TOKEN",
},
})
This integration exposes a minimal blink source that only consumes
append_chars (end-of-line ghost text). Complex diffs (multi-line edits,
replacements, deletions, cursor prediction UI) still render via the native UI.
require("cursortab").setup({
keymaps = {
accept = false, -- Let blink manage <Tab>
},
blink = {
enabled = true,
ghost_text = false, -- Disable native ghost text
},
})
require("blink.cmp").setup({
sources = {
providers = {
cursortab = {
module = "cursortab.blink",
name = "cursortab",
async = true,
-- Should match provider.completion_timeout in cursortab config
timeout_ms = 5000,
score_offset = 50, -- Higher priority among suggestions
},
},
},
})
:CursortabToggle: Toggle the plugin on/off:CursortabShowLog: Show the cursortab log file in a new buffer:CursortabClearLog: Clear the cursortab log file:CursortabStatus: Show detailed status information about the plugin and
daemon:CursortabRestart: Restart the cursortab daemon processTo build the server component:
cd server && go build
To run tests:
cd server && go test ./...
To run the E2E pipeline tests (ComputeDiff → CreateStages → ToLuaFormat):
cd server && go test ./text/... -run TestE2E -v
To record new expected output after changes:
cd server && go test ./text/... -run TestE2E -update
Updated fixtures are marked as unverified. After reviewing the generated
expected.json files and the HTML report, mark a specific fixture as verified:
cd server && go test ./text/... -run TestE2E -verify <name>
Verification state is tracked in server/text/e2e/verified.json (a SHA256
manifest). In the HTML report, verified passing tests are collapsed while
unverified or failing tests are shown expanded.
Each fixture is a directory under server/text/e2e/ with old.txt, new.txt,
params.json, and expected.json. Both batch and incremental pipelines are
verified against the same expected output. An HTML report is generated at
server/text/e2e/report.html.
To add a new fixture manually, create a directory with the four files and run
with -update to generate the initial expected.json, then review and
-verify-case=<name> to mark it verified.
provider.max_tokens to reduce output length (also limits input
context)The copilot provider is known to be slower than the rest.
:CursortabRestartprovider.completion_timeout (default: 5000ms) to 10000 or more if
your model is slowprovider.max_tokens to give the model more surrounding context
(trade-off: slower completions)Use your Neovim plugin manager to pull the latest changes, then restart Neovim.
The daemon will automatically restart if the configuration has changed. You can
also run :CursortabRestart to force a restart.
The plugin runs a background daemon that persists after Neovim closes.
Environment variables are only loaded when the daemon starts. If you add or
change an environment variable (e.g., SWEEPAPI_TOKEN in your .zshrc), run
:CursortabRestart to restart the daemon with the new environment variables.
Note: If you change plugin configuration (e.g., switch providers), the daemon
will automatically restart on the next setup() call.
Contributions are welcome! Please open an issue or a pull request.
Feel free to open issues for bugs :)
This project is licensed under the MIT License - see the LICENSE file for details.