Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vim-ai-complete

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.

Why this exists

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.

Requirements

  • Vim 9.2+ compiled with +job +channel +timers +popupwin (check with vim --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 个人中心 → 访问控制 → 访问令牌.

Install (vim-plug)

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.

Configuration

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 = 0

If 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.

Commands

  • :AICompleteToggle — turn the whole thing on/off at runtime

Wiring up a keybinding (no default is shipped)

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.

How it works

  • TextChangedI → debounce (g:ai_complete_debounce_ms) → cancel any in-flight request → build a prompt from the surrounding context → async curl via job_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 in ps 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 a popup_create() ghost-text overlay
  • Accept() splices the suggestion in at the cursor and appends any remaining lines below

Testing

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) exported

License

MIT

About

使用 https://www.modelscope.cn/my/access/token 免费额度小使用场景补全

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages