Copilot-style inline AI code/text completion for classic Vim (not Neovim). Ghost text appears while you type, accept with a keybinding you wire up yourself.
GitHub Copilot got disabled by organizational policy. The Neovim ecosystem has mature
alternatives (minuet-ai.nvim, etc.), but classic Vim doesn't — the community's energy
went into Neovim's Lua/virtual-text APIs. Vim 9's popup_create() + job_start() +
timer_start() are enough to build the same experience natively, so this plugin does:
async debounced request → popup-rendered ghost text → accept-on-demand, all without
touching the buffer until you actually accept.
- Vim 9.2+ compiled with
+job +channel +timers +popupwin(check withvim --version) curl- An API key for an OpenAI-compatible chat completions endpoint. Default config points at ModelScope API-Inference (mainland-China-direct, free tier 2000 requests/day, no VPN needed) — bind an Alibaba Cloud account, then generate a token under 个人中心 → 访问控制 → 访问令牌.
Plug 'nine2/vim-ai-complete'plug#begin()'s base directory must match wherever you actually keep the plugin locally if
you're developing against a not-yet-pushed clone — vim-plug resolves user/repo to
<plug_home>/repo, so as long as the directory name matches, :PlugInstall/:PlugUpdate
will only hit the network when you actually need to pull.
Set these in your vimrc before or after the Plug line (all have defaults):
let g:ai_complete_enabled = 1 " master on/off switch
let g:ai_complete_api_key = $MODELSCOPE_API_KEY " defaults to this env var
let g:ai_complete_model = 'Qwen/Qwen3-Coder-30B-A3B-Instruct'
let g:ai_complete_debounce_ms = 400 " typing pause before requesting
let g:ai_complete_context_before = 60 " lines of context before cursor
let g:ai_complete_context_after = 20 " lines of context after cursor
let g:ai_complete_max_lines = 12 " cap suggestion length
let g:ai_complete_excluded_ft = ['gitcommit'] " filetypes that never trigger
let g:ai_complete_debug = 0If you want a different backend, g:ai_complete_api_key + g:ai_complete_model are all
you need to change — the request is built as a standard OpenAI-compatible
chat/completions call (autoload/ai_complete.vim's BuildCurlConfig()), so anything
speaking that protocol (Groq, DeepSeek, OpenRouter, a local Ollama server, ...) works.
:AICompleteToggle— turn the whole thing on/off at runtime
This plugin deliberately does not bind any key itself. Ghost-text acceptance needs to coexist with whatever completion engine you already use (YCM, coc.nvim-style setups, UltiSnips snippet expansion, ...), and the right priority order is project-specific. Wire it into your own Tab-routing function, or a dedicated key — example modeled on this plugin's own dev setup (UltiSnips snippet > YCM popup > AI suggestion > plain Tab):
function! s:SmartTab()
call UltiSnips#ExpandSnippetOrJump()
if g:ulti_expand_or_jump_res > 0
return ""
endif
if pumvisible()
return "\<C-n>"
endif
if ai_complete#HasSuggestion()
return ai_complete#Accept()
endif
return "\<Tab>"
endfunction
imap <silent><expr> <Tab> <SID>SmartTab()
" Or a key that's always available regardless of other popups being open:
imap <silent><expr> <C-y> ai_complete#Accept()Important: always check HasSuggestion() before calling Accept() inside an <expr>
mapping, don't branch on Accept()'s return value — Accept() always returns '' (the
actual insertion is deferred to a timer_start(0, ...) callback, since Vim disallows
buffer/window mutation during <expr> mapping evaluation — E565). If you call Accept()
unconditionally and check its return value afterward, you'll silently double-insert a
literal Tab on top of the accepted suggestion.
TextChangedI→ debounce (g:ai_complete_debounce_ms) → cancel any in-flight request → build a prompt from the surrounding context → asynccurlviajob_start()- API key is passed via a
0600-permission curl config file (curl -K), never as a command-line argument, so it never shows up inps aux - Response is parsed, code-fence-stripped, and defensively checked for the model echoing
back text already before the cursor (
StripEchoedPrefix()) before being rendered as apopup_create()ghost-text overlay Accept()splices the suggestion in at the cursor and appends any remaining lines below
bash test/run_tests.sh # pure-function unit tests, no network
bash test/run_integration_test.sh # hits the real API — needs $MODELSCOPE_API_KEY (or
# whatever g:ai_complete_api_key resolves to) exportedMIT