Fransys

Blog technique — Architecture, Cloud & DevOps

BlogServicesContactÀ propos

Suivez-moi

githubGitHublinkedinLinkedinmailMail

© 2026 Fransys • Fransys

Fransys

Catégories

  • Tous les articles
  • Tags
  • productivite10
  • nas10
  • ia8
  • securite7
  • linux6
  • claude-code6
  • auto-hebergement6
  • neovim5
  • docker5
  • editeur4
  • mcp3
  • vpn3
  • reseau3
  • lua2
  • terminal2
neovimiaediteurproductivite

Complétion IA dans Neovim : Codeium, Gemini et nvim-cmp

Publié le
23 janvier 2026·6 min de lecture
Avatar François GUERLEZFrançois GUERLEZ

Pourquoi trois couches de complétion ?

Quand tu parles de complétion dans un éditeur, tu penses au LSP. C'est LE standard. Mais l'IA générative a explosé, et du coup les options se sont multipliées. Tu peux pas juste balancer tout dans un menu, ça serait un bordel.

J'ai donc opté pour un système à trois couches distincts. Pas une. Trois. Chacun avec sa job, sa façon d'interagir.

L'idée : chaque couche résout un problème spécifique. Le ghost text pour les suggestions passives qui arrivent naturellement. Un modèle IA dans le menu cmp pour les cas plus élaborés. Et nvim-cmp classique pour le LSP, les snippets, le reste.

Couche 1 : NeoCodeium (ghost text)

NeoCodeium. C'est le client Neovim pour Codeium. Pourquoi ça marche ?

  • Gratuit et illimité : pas de tokens à tracker. Pas d'abonnement qui t'oblige à payer.
  • Ghost text : les suggestions arrivent en grisé, directement dans le buffer. Pas de popup qui prend de la place.
  • Zéro conflit avec nvim-cmp : tu peux avoir les deux sans qu'ils se marchent dessus.

Le ghost text, c'est parfait pour les cas évidents : fermer une parenthèse, finir un nom de variable, du boilerplate basique. Tu acceptes avec un raccourci, ou tu continues à taper et elle 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. Alt-f pour accepter ? C'est devenu un reflexe. Rapide, facile d'accès, et ça gêne rien.

Couche 2 : Minuet AI (Gemini dans le menu cmp)

Minuet. Ça intègre Gemini directement dans le menu cmp de nvim. C'est pas comme le ghost text (qui est passif). Ici, 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,
}

Quelques détails importants :

  • Le modèle gemini-2.5-flash offre un bon équilibre vitesse vs qualité
  • La clé API vient de la variable d'env GEMINI_API_KEY
  • enable_auto_complete = false : Gemini ne s'active pas automatiquement. Tu l'appelles avec Alt-y dans le menu cmp.

C'est délibéré. L'IA générative, c'est utile pour des suggestions plus compliquées (refactoring, patterns complexes, genérer de la doc). Mais tu veux pas qu'elle ralentisse ton workflow normal. En la rendant on-demand, tu restes aux commandes.

Couche 3 : nvim-cmp (le moteur principal)

Au cœur du système, il y a nvim-cmp. C'est LE moteur. Il orchestre toutes les sources. C'est lui qui gère le popup, la navigation, la 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,
}

Plusieurs choix clés ici :

  • Sources par priorité : Minuet (IA) d'abord, puis LSP, LuaSnip, path, buffer en fallback
  • Fenêtres bordées : le menu cmp et le popup de doc ont des bordures. C'est plus lisible.
  • Timeout 2 secondes : fetching_timeout = 2000 garantit qu'une source IA qui traine pas de l'épauler ne bloque le workflow
  • Tab/S-Tab intelligents : ils naviguent dans le menu cmp quand il est ouvert. Sinon, ils sautent entre les positions de snippets LuaSnip

LuaSnip : le moteur de snippets

En parallèle de la complétion, 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 on navigue entre les positions avec Tab/S-Tab grâce au mapping décrit plus haut.

Comment tout ça coexiste

Le truc clé de cette archi, c'est que les trois couches ne se gênent pas :

  • NeoCodeium affiche du ghost text directement dans le buffer. Touch pas au menu cmp.
  • nvim-cmp gère le popup de complétion classique : LSP, snippets, path.
  • Minuet s'intègre comme une source cmp. Mais elle se déclenche juste manuellement avec Alt-y.

Dans la pratique, quand je code :

  1. Le ghost text Codeium apparaît en grisé. C'est bon ? Alt-f pour accepter.
  2. Je veux des résultats LSP ? Le menu cmp s'ouvre auto ou je fais C-Space.
  3. J'ai besoin d'une suggestion IA plus fancy ? Alt-y interroge Gemini sans quitter le menu cmp.

Le timeout de 2 secondes sur cmp ? C'est important. Si Gemini traîne, la complétion continue avec les autres sources. Pas de freeze. Pas d'attente. Du flow continu.

La conclusion

Ce système à trois couches me donne le meilleur des deux mondes : l'IA pour les suggestions intelligentes, le LSP pour la précision. Chaque couche a son propre mode d'interaction. Pas de surcharge mentale. Tu sais toujours où vient la suggestion et comment l'accepter. C'est un setup qui tourne tous les jours sur mes projets TypeScript et Python sans ralentir une seule seconde.

Article précédent

← Pare-feu et Fail2ban : verrouiller les accès réseau du NAS

Article suivant

Durcir le noyau Linux de son NAS selon les normes CIS Level 2→
← Retour au blog

Sommaire

  • Pourquoi trois couches de complétion ?
  • Couche 1 : NeoCodeium (ghost text)
  • Couche 2 : Minuet AI (Gemini dans le menu cmp)
  • Couche 3 : nvim-cmp (le moteur principal)
  • LuaSnip : le moteur de snippets
  • Comment tout ça coexiste
  • La conclusion