
Complétion IA dans Neovim : Codeium, Gemini et nvim-cmp
Pourquoi trois couches de complétion
Le LSP reste le standard de la complétion, mais l'IA générative a multiplié les options — et tout empiler dans un seul menu devient vite illisible.
D'où un système à trois couches distinctes, chacune résolvant un problème spécifique : le ghost text pour les suggestions passives, un modèle IA dans le menu cmp pour les cas élaborés, et nvim-cmp classique pour le LSP, les snippets et le reste.
Couche 1 : NeoCodeium (ghost text)
NeoCodeium est le client Neovim pour Codeium : gratuit et illimité (pas de tokens à tracker), en ghost text grisé directement dans le buffer plutôt qu'en popup, et sans conflit avec nvim-cmp.
Le ghost text couvre les cas évidents : fermer une parenthèse, finir un nom de variable, du boilerplate. On accepte avec un raccourci, ou on continue à taper et la suggestion disparaît.
{
"monkoose/neocodeium",
event = "VeryLazy",
config = function()
local neocodeium = require("neocodeium")
neocodeium.setup()
-- Alt-f pour accepter la suggestion
vim.keymap.set("i", "<A-f>", neocodeium.accept)
-- Alt-n / Alt-p pour naviguer entre les suggestions
vim.keymap.set("i", "<A-n>", neocodeium.cycle_or_complete)
vim.keymap.set("i", "<A-p>", function()
neocodeium.cycle_or_complete(-1)
end)
-- Alt-c pour effacer la suggestion
vim.keymap.set("i", "<A-c>", neocodeium.clear)
end,
}
Les keybindings sont choisis pour éviter tout conflit avec nvim-cmp.
Couche 2 : Minuet AI (Gemini dans le menu cmp)
Minuet intègre Gemini dans le menu cmp. Contrairement au ghost text passif, c'est une source on-demand qui apparaît dans le même menu que le LSP.
{
"milanglacier/minuet-ai.nvim",
config = function()
require("minuet").setup({
provider = "gemini",
provider_options = {
gemini = {
model = "gemini-2.5-flash",
api_key = "GEMINI_API_KEY",
},
},
cmp = {
enable_auto_complete = false,
},
})
end,
}
Le modèle gemini-2.5-flash offre un bon équilibre vitesse/qualité, la clé API vient de la variable d'environnement GEMINI_API_KEY, et enable_auto_complete = false empêche Gemini de s'activer automatiquement — on l'appelle avec Alt-y dans le menu cmp.
C'est délibéré : l'IA générative est utile pour les suggestions compliquées (refactoring, patterns complexes, génération de doc), mais ne doit pas ralentir le workflow normal.
Couche 3 : nvim-cmp (le moteur principal)
nvim-cmp orchestre toutes les sources : popup, navigation, confirmation.
{
"hrsh7th/nvim-cmp",
event = "InsertEnter",
dependencies = {
"hrsh7th/cmp-nvim-lsp",
"hrsh7th/cmp-buffer",
"hrsh7th/cmp-path",
"saadparwaiz1/cmp_luasnip",
},
config = function()
local cmp = require("cmp")
local luasnip = require("luasnip")
cmp.setup({
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
completion = { completeopt = "menu,menuone,noinsert" },
window = {
completion = cmp.config.window.bordered(),
documentation = cmp.config.window.bordered(),
},
performance = {
fetching_timeout = 2000,
},
mapping = cmp.mapping.preset.insert({
["<C-n>"] = cmp.mapping.select_next_item(),
["<C-p>"] = cmp.mapping.select_prev_item(),
["<C-Space>"] = cmp.mapping.complete(),
["<C-e>"] = cmp.mapping.abort(),
["<CR>"] = cmp.mapping.confirm({ select = true }),
["<A-y>"] = require("minuet").make_cmp_map(),
["<Tab>"] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item()
elseif luasnip.expand_or_jumpable() then
luasnip.expand_or_jump()
else
fallback()
end
end, { "i", "s" }),
["<S-Tab>"] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_prev_item()
elseif luasnip.jumpable(-1) then
luasnip.jump(-1)
else
fallback()
end
end, { "i", "s" }),
}),
sources = cmp.config.sources({
{ name = "minuet" },
{ name = "nvim_lsp" },
{ name = "luasnip" },
{ name = "path" },
}, {
{ name = "buffer" },
}),
})
end,
}
Quatre choix structurants : les sources ordonnées par priorité (Minuet, puis LSP, LuaSnip, path, et buffer en fallback), des fenêtres bordées pour la lisibilité, un timeout de 2 secondes (fetching_timeout = 2000) qui empêche une source IA lente de bloquer le workflow, et des Tab/S-Tab intelligents qui naviguent dans le menu cmp quand il est ouvert, et sautent entre les positions de snippets sinon.
LuaSnip : le moteur de snippets
LuaSnip gère les snippets, avec le pack friendly-snippets qui fournit des snippets VSCode-style pour tous les langages courants.
{
"L3MON4D3/LuaSnip",
build = "make install_jsregexp",
dependencies = {
{
"rafamadriz/friendly-snippets",
config = function()
require("luasnip.loaders.from_vscode").lazy_load()
end,
},
},
}
Les snippets se déclenchent via nvim-cmp et la navigation entre positions se fait au Tab/S-Tab grâce au mapping ci-dessus.
Comment tout ça coexiste
Les trois couches ne se gênent pas : NeoCodeium affiche du ghost text dans le buffer sans toucher au menu cmp, nvim-cmp gère le popup classique (LSP, snippets, path), et Minuet s'intègre comme source cmp mais ne se déclenche que manuellement.
En pratique, pendant qu'on code :
- Le ghost text Codeium apparaît en grisé —
Alt-fpour accepter. - Pour les résultats LSP, le menu cmp s'ouvre automatiquement ou via
C-Space. - Pour une suggestion IA plus élaborée,
Alt-yinterroge Gemini sans quitter le menu.
Le timeout de 2 secondes est important : si Gemini traîne, la complétion continue avec les autres sources, sans freeze.
Conclusion
Ce système à trois couches combine l'IA pour les suggestions intelligentes et le LSP pour la précision, chaque couche ayant son propre mode d'interaction. On sait toujours d'où vient une suggestion et comment l'accepter — un setup qui tourne au quotidien sur des projets TypeScript et Python sans ralentissement.
Articles similaires