1. Introduction

Tous les développeurs ont été, un jour ou l'autre, confrontés à Vim, et pour 99 % d'entre eux, ont pris peur en essayant de l'utiliser. Il faut dire, en effet, que les premières heures d'utilisation sont assez douloureuses. Cependant, il suffit de faire du pair-programming avec une personne maîtrisant l'outil pour se rendre compte instantanément de la puissance que cela confère. Nous allons voir dans cet article, comment configurer Neovim pour profiter de toute la puissance de Vim dans un IDE proposant toutes les fonctionnalités d'un IDE classique, le tout sans quitter votre terminal.

2. Vim

Vim est un éditeur de texte basé dans le terminal, version améliorée de Vi. La philosophie de Vim est simple : Fournir un système d'édition de fichiers se basant uniquement sur des combinaisons de touches, pour s'abstraire totalement de la souris. De nombreux développeurs ayant ouvert par erreur (ou par excès de confiance) Vim la première fois se sont vite retrouvés embêtés, ne sachant pas comment quitter le fichier et récupérer leur terminal. Cette situation est une source intarissable de Memes. Pour se rendre compte de ce traumatisme, il suffit de voir sur l'image suivante que Vim et Vi arrivent en 2ᵉ et 4ᵉ place dans la recherche Google devant des sujets touchant le grand public. Escape vim

On ne va pas se mentir, il y a un coût à payer pour parvenir à éditer et se déplacer aisément dans un fichier avec les Vim Motions. Heureusement, de nombreuses ressources (comme le VimTutor) existent pour vous former à leur utilisation. Une fois ces combinaisons maîtrisées, vous pourrez voir votre productivité (et votre Developper Swag) augmenter considérablement.

3. NeoVim

Nous allons maintenant rentrer dans le vif du sujet : Neovim 😍. Admettons que vous vous sentiez assez à l'aise pour considérer l'utiliser au quotidien (ou que vous soyez prêt à investir du temps et de l'effort), Neovim peux venir remplacer votre IDE favori. Une fois installé (en suivant la méthode de votre choix), vous pouvez lancer ce magnifique outils, en tapant nvim dans votre terminal.

Et voilà.

default neovim

Bon, on est d'accord, ce n'est pas hyper sexy, ni utilisable pour l'instant. Allons-y pour configurer tout ça.

La configuration de Neovim est écrite en lua et se situe par défaut sous ~/.config/nvim/. le fichier init.lua sera le point d'entrée de notre configuration.
Créons un fichier sous lua/plugins.lua qui nous servira à configurer nos plugins. Ce fichier sera simplement ajouté dans init.lua via:

require "plugins"

Plugin Manager

Nous allons avoir besoin d'un Plugin Manager afin de configurer notre IDE. Dans notre exemple, nous utiliserons lazy.nvim. Le setup initial est assez simple. La méthode setup prendra en argument la liste de nos plugins.

Commençons par un thème, pour rendre tout cela un peu plus joli. Pour ne pas perturber nos amis venant de VSCode, j'ai choisi le thème vim-code-dark qui reprend le thème One Dark de VSCode.

colorscheme

Nous voyons ici la différence, une fois la commande suivante exécutée :

:colorscheme codedark

4. Configuration

Maintenant que notre plugin manager est opérationnel, nous allons pouvoir rajouter des plugins, en les rajoutant dans le tableau de la méthode setup. Tout nouveau plugin sera automatiquement chargé au prochain démarrage de Neovim.

Telescope

Afin de rendre notre Neovim utilisable, le premier plugin INDISPENSABLE est sans aucun doute Telescope. Il s'agit du plugin qui nous permettra de rechercher dans notre projet, que ce soit des fichiers, du texte, et bien d'autres choses. Il existe une palette de configuration énorme pour ce plugin, nous verrons ici une version simpliste. Pour dire à Telescope de se lancer, ainsi que pour lui donner sa configuration spécifique, on va créer un fichier de configuration sous after/plugin/telescope.rc.lua dont voici le contenu.

local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>ff', builtin.find_files, {})
vim.keymap.set('n', '<leader>fg', builtin.live_grep, {})
vim.keymap.set('n', '<leader>sd', builtin.diagnostics, {})

Je n'ai pas pour l'instant introduit la notion de leader key. Il s'agit d'une touche du clavier permettant à l'utilisateur de composer des combinaisons spécifiques de touches pour réaliser des actions.

Nous pouvons configurer dans le fichier init.lua cette leader key, qui sera mappée sur la touche Espace dans notre cas.

vim.g.mapleader = " "

En tapant Espace -> f -> f (pour find file), la fenêtre de recherche de fichier s'ouvre. La recherche peut se configurer pour utiliser du fuzzy-finding, ce qui la rend vraiment très puissante.

telescope finder

Neo-Tree

Nous pouvons désormais ouvrir des fichiers en tapant leur nom via Telescope. Ce n'est toujours pas optimal pour se repérer dans son projet. Pour cela, nous allons configurer un arbre de fichiers. Des dizaines de solutions existent, mais nous allons en voir un exemple, avec neo-tree.nvim.

Comme pour Telescope, créons une configuration sous after/plugins/neotree.rc.lua avec la configuration hyper minimaliste une nouvelle fois :

require("neo-tree").setup {
  popup_border_style = "rounded",
  enable_git_status = true,
  enable_diagnostics = true,
  window = {
    position = "left",
    width = 40,
    mapping_options = {
      noremap = true,
      nowait = true,
    },
  },
}
 
keymap.set("n", "<leader>e", ":Neotree<Return>", { silent = true })
 
neotree

En tapant la commande Espace -> e (pour Explorer), nous pouvons un bel arbre de fichiers. Par défaut, ce plugin vient avec des raccourcis pour :

  • a (add) Ajouter un fichier (ou un dossier en finissant par /)
  • d (delete) Supprimer un fichier un fichier ou dossier.
  • r (rename) Renommer un fichier ou dossier.
  • y (yank) et p (paste) pour copier et coller des fichiers. et bien d'autres.

Nous pouvons à présent effectuer toutes les actions de gestion de fichiers dans nos projets, sans quitter notre IDE, grâce à ces 2 plugins.

TreeSitter

Pour l'instant, nous avons un bon éditeur de fichiers. Cependant, les langages de programmation sont pleins de spécificités, qui vont impacter la coloration et l'analyse syntaxique. nvim-treesitter est un plugin (ou plutôt une suite de plugins) permettant d'analyser les fichiers de codes, en utilisant un système de Parsers spécifique par langage.

Voici le fichier de configuration minimaliste after/plugins/treesitter.rc.lua

require("nvim-treesitter.configs").setup {
  ensure_installed = { "lua", "vim", "javascript", "typescript" },
 
  sync_install = false,
 
  auto_install = true,
}

Au démarrage de l'application, Treesitter va télécharger les parser des langages demandés. En observant l'image suivante, présentant un avant/après du même fichier, avec et sans Treesitter. On constate que la coloration est différente, car à présent, chaque bout de code est caractérisé comme un élément syntaxique et sera interprété comme il se doit par le thème de couleur.

treesitter

Nous allons, pour la suite, utiliser du TypeScript.

Treesitter vient également avec une command :Inspect afin de voir l'analyse du fichier. Cette analyse est une mine d'or pour toute personne voulant développer un plugin spécifique !

tree

Il faut savoir que l'analyse du fichier se fait de manière incrémentale. De nombreux plugins se basent sur TreeSitter, pour faire par exemple :

  • du renommage de symbole
  • des snippets
  • ...

LSP

Notre éditeur commence à prendre forme. Cependant, nous ne pouvons faire des actions qu'avec le texte déjà présent dans le fichier. Pour pousser plus loin, nous allons voir une nouvelle notion fondamentale : Les Language Server Protocols (LSP) Il s'agit de serveurs tournant en tâches de fond sur nos machines, qui vont permettre de prendre notre code source et d'y rajouter des fonctionnalités "intelligentes" propres à notre langage.

Pour cela, nous allons intégrer à notre configuration 3 nouveaux plugins :

Mason

Mason est un package manager, qui va permettre d'installer les LSP dont nous aurons besoin. Voici le contenu du fichier after/plugins/mason.rc.lua:

local status, mason = pcall(require, "mason")
if not status then
  return
end
local status2, lspconfig = pcall(require, "mason-lspconfig")
if not status2 then
  return
end
 
mason.setup {}
 
lspconfig.setup {
  ensure_installed = {
    "typescript",
  },
}

Nous demandons ici à Mason de s'assurer que le LSP pour Typescript est bien installé.

En lançant la commande suivante, nous obtenons une interface nous permettant de visualiser nos LSP. Elle nous permet également d'en télécharger de nouveaux.

:Mason
Mason

mason-lspconfig

L'intérêt de ce plugin est de faire le lien entre Mason et lspconfig afin de s'assurer que les LSP installés par Mason sont bien configurés comme lspconfig l'attend.

lspconfig

Ce plugin réalise l'intégration entre nos LSP et l'éditeur de texte. Créons un fichier de configuration générale de nos LSP :

local status, nvim_lsp = pcall(require, "lspconfig")
if not status then
  return
end
 
local protocol = require "vim.lsp.protocol"
 
local on_attach = function(client, bufnr)
  -- Mappings.
  local opts = { noremap = true, silent = true }
 
  -- See `:help vim.lsp.*` for documentation on any of the below functions
  buf_set_keymap("n", "gD", "<Cmd>lua vim.lsp.buf.declaration()<CR>", opts)
  buf_set_keymap("n", "gd", "<Cmd>lua vim.lsp.buf.definition()<CR>", opts)
  buf_set_keymap("n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", opts)
  buf_set_keymap("n",  "<leader>ca", "<cmd>lua vim.lsp.buf.code_action()<CR>", opts)
 
end
 
nvim_lsp.tsserver.setup {
  on_attach = on_attach,
  filetypes = { "typescript", "typescriptreact", "typescript.tsx" },
  cmd = { "typescript-language-server", "--stdio" }
}

La méthode on_attach définie dans ce fichier sera attachée à tous les buffers (tous les fichiers). La configuration du LSP pour Typescript tsserver se fait en bas de fichier. A chaque fois que nous ouvrirons un fichier .t ou .tsx ou que des modifications sont apportées, le tsserver sera interrogé. Prenons l'exemple suivant :

diagnostics

Le code se trouvant dans ce fichier contient une erreur de type. Le tsserver va la repérer et remonter les informations à Neovim pour les remonter à l'affichage. On constate désormais en lançant la combinaison Espace -> s -> d (show diagnostics) définie dans notre configuration Telescope, la fenêtre s'ouvre et affiche la liste des erreurs présentes dans tous les buffers ouverts.

Les LSP donnent énormément de possibilités, bien trop pour toutes les énumérer ici. Je vais en détailler seulement 3 ici :

  • Code actions : En positionnant notre curseur sur une erreur (ex : une classe ou fonction non importée) et en lancant Espace -> c -> a (Code actions), une fenêtre s'ouvrira pour proposer une liste d'actions pour résoudre cette erreur (Dans ce cas, il proposera l'import de la classe ou fonction manquante)
code actions
  • Go to definition: En positionnant le curseur sur un symbole (ex: le nom d'une fonction), et en tapant g -> d, nous serons automatiquement renvoyés vers la définition de cette fonction (même si celle ci est faite dans un autre fichier).
  • Go to references: En positionnant le curseur sur un symbole (ex: le nom d'une fonction), et en tapant g -> r, une fenêtre s'ouvrira affichant la liste des références de cette fonction, permettant de naviguer, et en sélectionnant, d'être déplacé directement à cette référence.

J'espère que vous êtes maintenant convaincu de la puissance des LSP et que vous comprenez maintenant mieux ce qui se trame en arrière-plan de votre IDE.

CMP

Une des fonctionnalités primordiales que nous n'avons pas encore dans notre IDE est l'autocomplétion. Pour cela, nous allons installer la suite de plugins cmp. Nous allons le configurer dans un fichier after/plugins/cmp.rc.lua

local cmp = require "cmp"
 
local WIDE_HEIGHT = 40
 
cmp.setup {
  window = {
    completion = cmp.config.window.bordered {
      winhighlight = "Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None",
    },
  },
  mapping = cmp.mapping.preset.insert {
    ["<C-Space>"] = cmp.mapping.complete(),
    ["<C-e>"] = cmp.mapping.abort(),
    ["<CR>"] = cmp.mapping.confirm { select = true },
  },
  sources = cmp.config.sources({
    { name = "nvim_lsp" },
  }, {
    { name = "buffer" },
  }),
}

Lors de la prochaine ouverture, nous aurons accès à de l'autocomplétion, en tirant parti de la puissance des LSP.

cmp

ESLint

Dans un projet de grande taille, il est très probable que vous ayez à configurer ESLint. Nous pouvons rajouter dans le fichier lspconfig.lua la configuration suivante, permettant de demander à Eslint de corriger tous les problèmes de lin avant chaque sauvegarde. Une spécificité néanmoins, ESLint nécessite d'avoir sur votre machine vscode-langservers-extracted installé. En effet, le LSP de Eslint n'est pour l'heure, pas disponible via Mason.

nvim_lsp.eslint.setup {
  on_attach = function(client, bufnr)
    vim.api.nvim_create_autocmd("BufWritePre", {
      buffer = bufnr,
      command = "EslintFixAll",
    })
  end,
  settings = {
    workingDirectory = {
      mode = "location",
    },
  },
  root_dir = nvim_lsp.util.find_git_ancestor,
}

Et voilà, Eslint est configuré et nous remonte bien les problèmes.

eslint

Copilot

Il est également possible de connecter Github Copilot, via copilot.vim afin de vivre une expérience de développement survitaminée.

copilot

6. Ce que cela nous a apporté ?

Cette période de configuration, de découverte des différents plugins en lisant leur documentation, m'ont permis de comprendre ce qui se passe au sein d'un IDE, chose que nous tenions pour acquis sur les IDEs "classiques". Une simple action comme le Ctrl -> click pour rentrer dans la définition d'une fonction semble triviale, mais nous savons maintenant les différentes parties prenantes pour réaliser ces actions.

La configuration que nous venons d'écrire est "As Code", nous pouvons facilement la déposer dans un répertoire Git. Nous pourrons donc utiliser très facilement notre configuration personnelle sur plusieurs machines sans avoir à refaire une quelconque configuration. En se documentant et en découvrant un nouveau plugin, ou une nouvelle configuration possible, nous pouvons incrémentalement améliorer notre configuration par petites touches pour s'approcher de la configuration parfaite (pour vous! chaque développeur aura la sienne)

De plus, l'évolution dans un environement Vim, avec ses raccourcis, permet de progresser dans nos fichiers et de les éditer Blazingly Fast ⚡️, résultant en une augmentation significative de productivité.

7. Conclusion

On va s'arrêter là, on pourrait continuer encore sur des pages et des pages. Il existe des centaines de plugins, certains plus ou moins utiles, afin de permettre à toute personne de configurer son éditeur pour répondre le mieux à ses propres besoins. Vous êtes maintenant fin prêt pour jouer au GigaChad avec votre éditeur dans le terminal et vous pourrez désormais prendre de haut les autres développeurs.

La configuration présentée dans cette introduction est une version simplifiée de ma configuration que vous pouvez trouver ici, qui elle-même dérive de celle de gmrdn, que je remercie de m'avoir fait découvrir cet univers 🙏.

Si vous souhaitez voir ce que cela donne, sans passer par la phase de configuration, des distributions Neovim toutes configurées existent pour faciliter le démarrage, telles que :

En espérant, vous avoir donné envie de faire le grand saut, ou du moins de vous avoir permis de mieux comprendre le fonctionnement d'un IDE.