Skip to content

Releases: teocns/neocursor.nvim

v0.4.0 — saying no

Choose a tag to compare

@teocns teocns released this 04 Aug 08:03
2e2ef13

There was no discoverable way to reject a suggestion. <C-]> was bound and worked, but it lived in one un-highlighted README table row and never appeared beside a suggestion — so the reasonable conclusion was that dismissal did not exist.

<Esc> now dismisses, and the suggestion says so:

⟪neocursor · <Tab> accept · <Esc> dismiss⟫

No <Esc> mapping was installed

This is the point. <Esc> already dismissed — leaving insert mode clears the suggestion — it just filed nothing, so the identical edit came straight back when you re-entered insert. What changed is that it now counts. The key was always working; nothing about your <Esc> is intercepted.

That matters, because a conditional <Esc> would have cost real things. In VS Code, Escape is a free key. In neovim it is the mode transition:

if <Esc> were mapped
macros non-deterministic — whether a ghost is showing depends on a debounced network reply
im-select, snippet plugins clobbered; their insert-mode <Esc> never runs
fallthrough no clean form — feedkeys "n" skips their mapping, "m" re-enters ours

Advertising behaviour that already exists costs none of that.

<C-]> stays, as the dismiss-without-leaving-insert variant for when you want to keep typing. It is the key copilot.vim, copilot.lua and avante.nvim all use.

Dismissal is tiered

The first dismiss clears the edit and keeps the jump target. Dismiss again with nothing showing and the target goes too. That is Cursor's split, not an invention: their Escape handler files the suggestion as rejected and then calls maybeShowHintLineWidget immediately after, deliberately keeping the prediction alive.

It takes the hint

Rejecting the same edit twice already suppressed it. New in this release is the gate that catches the model being wrong in a new way every time — Cursor's hasRejectedTooManySuggestions. After 20 dismissals with nothing accepted in between, neocursor stops volunteering on the passive triggers:

trigger after the budget
entering insert silent
moving to another line silent
typing still asks

Accepting anything refills it, as does switching buffers. :NeocursorLog shows the tally live and flips to MUTED at the threshold. The 20 is not invented — it now arrives with the rest of CppConfig, and the live backend returns exactly that.

On Cursor under a vim layer

Worth knowing if you also use Cursor: its handleKeyDownForCppKeys is a raw document keydown listener rather than a registered keybinding, and the suggestion branch never calls preventDefault — only the auto-import branch does. So with vscode-neovim, Escape dismisses the suggestion and propagates, leaving insert mode. Both happen. Cursor's tiering collapses to one tier for vim users by accident, which is precisely the behaviour this release makes deliberate here.

One deliberate divergence

Cursor increments its counter on every clearSuggestions(). This counts dismissals instead. Their suggestion survives typing via isOnShortestEditPath; ours only does for inline ghosts — a diff is dropped and refetched per keystroke, so counting clears would mute you after ~20 characters rather than ~20 ignored suggestions. Same intent, adjusted for where the two renderers differ; documented at the call site.

On not muting jumps you never refused

The first draft of this change filed a prediction rejection on every InsertLeave. Two ordinary trips through normal mode and the jump target went dead for the 30s pred_rejects TTL. flow_spec round 4 caught it, and a baseline comparison confirmed the regression was mine rather than flake — which is the whole reason that round asserts the prediction survives a chain. Fixed by splitting reject_suggestion() from reject_prediction().

Full CI matrix green on ubuntu, windows and macos.

Full changelog: v0.3.0...v0.4.0

v0.3.0 — hide the hints

Choose a tag to compare

@teocns teocns released this 29 Jul 13:56
d6782f1

Adds show_hints, requested by @scc02 in #4.

The ⟪neocursor · <Tab> accept⟫ label and the ⟪<Tab> → L42⟫ prediction pill are there to teach you the keys. Once you know them, they're noise. Now you can turn them off.

require("neocursor").setup({
  show_hints = false,                  -- hide both
  -- show_hints = { edit = false },    -- hide the label, keep the jump pill
})

What each surface costs you

Surface Renders Hiding it costs
edit ⟪neocursor · <Tab> accept⟫ nothing — the diff beside it already shows the change
prediction ⟪<Tab> → L42⟫ the only on-screen sign a jump is queued

show_hints = false hides both. If you want the label gone but still want to see where <Tab> will land, use { edit = false }.

Suggestions are untouched either way: ghost text, diffs, and every <Tab> behavior are identical with hints on or off. :NeocursorDebug prints the resolved setting.

On not breaking <Tab>

show_prediction() both paints the pill and returns the boolean gating jumps at four call sites, so the naive way to hide a pill is to silently disable jumping. The paint is conditional; the return value isn't. flow_spec.lua now reruns its full 20-assertion behavioral suite with chrome disabled, and a new hints_spec.lua covers the render layer — both on ubuntu/windows/macos.

Full changelog: v0.2.0...v0.3.0

v0.2.0 — Linux and Windows

Pre-release

Choose a tag to compare

@teocns teocns released this 28 Jul 07:17
9275de7

neocursor now runs on Linux and Windows. Previously the sidecar only looked for Cursor's session in the macOS app-support path, so that was the only place it could ever find a login. Thanks @Aktyn for the report (#2) — and for confirming that ~/.config/Cursor was the missing piece.

Nothing to configure. Update and it finds your session:

Cursor data dir
macOS ~/Library/Application Support/Cursor
Linux $XDG_CONFIG_HOME/Cursor~/.config/Cursor
Windows %APPDATA%\Cursor

Insiders builds, the lowercase cursor directory some community Linux packages create, and Flatpak/Snap sandbox roots are detected too.

Unusual installs

Portable copies, or Windows-side Cursor seen from WSL:

export CURSOR_CONFIG_DIR="/mnt/c/Users/me/AppData/Roaming/Cursor"
# or point straight at the SQLite file:
export CURSOR_STATE_DB_PATH="/path/to/User/globalStorage/state.vscdb"

Better failures

"Cursor isn't where we looked" used to be a Python traceback in a log nobody opens. Now it's one line naming every path checked plus the overrides, surfaced through vim.notify when the sidecar dies during startup — so a broken setup tells you what's wrong instead of the plugin going silently inert. uv run cursor_paths.py prints the full resolution for bug reports.

Windows fixes beyond the path

Three things a path swap alone would have hit:

  • SQLite URI was built by hand, which breaks on C:\… backslashes and a bare drive letter. Now Path.as_uri() — which also fixed the unescaped space in "Application Support".
  • stdio inherited the platform default: Windows text mode rewrote the \n record separator to \r\n, and a POSIX C locale downgraded to ASCII, mangling non-ASCII buffers. Now pinned to UTF-8 + LF.
  • The macOS DNS workaround shelled out to dig/host everywhere — missing binaries and flashing console windows on Windows. Gated to macOS.

Also: the lazy.nvim build snippet used 'httpx[http2]', and single quotes aren't quotes in cmd.exe. The README now uses double quotes, valid in both sh and cmd. (The vim.pack recipe was already safe — it uses list form.)

Testing

CI now runs on ubuntu + windows + macos. Since no runner has anyone signed into Cursor, the specs synthesize a credential-free install and drive the real sidecar through it, covering path resolution, the SQLite URI, LF framing, and the full 20-assertion tab-flow spec on real Neovim.

Linux was additionally verified end-to-end against the live Cursor backend with a real session at ~/.config/Cursor; test/docker_linux.sh reproduces that from any checkout.

Honest caveat: Windows is proven by CI mechanics, not by a human in a Windows GUI Neovim with a real Cursor login. If you're on Windows, a confirmation (or a bug report) is very welcome.

Full changelog: v0.1.0...v0.2.0

v0.1.0 — beta

v0.1.0 — beta Pre-release
Pre-release

Choose a tag to compare

@teocns teocns released this 24 Jul 12:17
b080dd2

neocursor.nvim v0.1.0 — first beta.

Cursor's Tab — the real next-edit model — inside Neovim, driven through Cursor's own StreamCpp backend with your existing Cursor login. No API key, no model to choose, no account to create.

Install (lazy.nvim)

{
  "teocns/neocursor.nvim",
  event = "InsertEnter",
  build = "uv run --with 'httpx[http2]' python -c 'import httpx'",
  opts = {},
}

Pin to releases with version = "*".

Working

  • Inline multi-line completions + <Tab> accept
  • Diff-style rewrites of existing lines
  • Next-edit prediction + cursor jump (the tab-tab-tab flow), multi-edit chains
  • Full context parity: nearby files, diff history, linter errors
  • Cursor-faithful request gating + newest-wins abort/rescue
  • Partial (word-by-word) accept

Known limits (beta)

  • macOS only (Cursor auth path)
  • Character-level single-char diffs — in progress
  • Cross-file apply on jump targets — partial

Requires: a signed-in Cursor install + uv. See the README for the full Cursor-Tab parity scoreboard.