Aller au contenu

Serveurs de langage

Textchum valide le code via le Language Server Protocol, avec un comportement fondateur : une instance de serveur par projet.

Une instance par projet

Les processus serveurs sont identifiés par (serveur, racine du projet), avec la même notion de projet que le navigateur : le répertoire ancêtre le plus proche portant un marqueur de racine. Ouvrez des fichiers de deux projets Rust différents et deux processus rust-analyzer indépendants tournent, chacun initialisé avec sa propre racine, chacun ne voyant que les fichiers de son projet. Les fuites entre projets — des diagnostics d'un espace de travail débordant dans un autre, un index bâti sur tout le répertoire personnel — sont impossibles par construction.

Les fichiers hors de tout projet reçoivent une instance par répertoire ; les fichiers isolés ne rejoignent donc jamais l'espace de travail de quelqu'un d'autre.

Le fichier d'une bibliothèque est l'exception : une crate du registre de cargo, les sources de la bibliothèque standard, un paquet dans site-packages ou node_modules, un en-tête de SDK. Aller à la définition y mène souvent, et il est servi par le serveur du projet d'où l'on vient — qui connaît déjà le fichier comme dépendance —, et non par un serveur lancé sur le répertoire de la bibliothèque, qui lirait la crate comme un projet à part et se mettrait à la compiler. Le fichier garde aussi l'arbre et les réglages du projet depuis lequel on l'a atteint.

Les projets imbriqués dans un autre — les membres d'un workspace uv ou Cargo, une fois que manifest projects en a fait des projets — sont le seul cas où l'on choisit. Quand le projet extérieur a recursive config, ils prennent ses entrées de serveur et c'est son instance qui s'en occupe : un serveur en haut, lancé là, qui voit tous les membres et l'environnement qu'ils partagent. Avec separate nested servers en plus, ils gardent les entrées et chacun les exécute pour lui-même, dans son dossier, pour des membres qui portent chacun leur environnement. Un projet imbriqué qui a sa propre entrée reste toujours à part. Les deux interrupteurs existent par projet et par défaut, dans Settings ▸ Projects et à côté de chaque entrée de projet dans Settings ▸ Language Servers ; voir configuration.

Ce que l'on voit

  • Les résultats arrivent pendant la frappe (envoyés par lots avec temporisation) et encadrent le texte concerné dans leur couleur : rouge pour les erreurs, orange pour les avertissements, bleu pour les notes. Une marque reste sur le code qu'elle signale pendant que l'on édite au-dessus ou autour, jusqu'au prochain rapport du serveur ; un lint qui ne tourne qu'à l'enregistrement (celui de cargo, par exemple) se rafraîchit à l'enregistrement suivant.
  • Le sous-titre de la fenêtre les compte (« 2 errors, 1 warning »).
  • La complétion pendant la frappe : les suggestions apparaissent après les caractères d'identifiant et ., filtrées au fil de la frappe — ↑/↓ pour choisir, ⏎ ou ⇥ pour accepter, ⎋ pour fermer, ⌃Espace pour les demander explicitement. Un élément accepté remplace la portion que nomme le serveur (une complétion postfix avale le receveur et le point) et apporte ses éditions supplémentaires, un import par exemple. La liste est ordonnée selon la façon dont chaque élément correspond à ce qui a été tapé — le nom entier, puis son début, puis les débuts de ses mots (rd trouve read_dir), puis n'importe où —, ensuite selon ce qui a déjà été choisi dans le projet, ensuite selon l'ordre du serveur lui-même. Chaque ligne montre les paramètres de l'élément, son type et, pour un élément qui apporte un import, d'où il vient. La documentation de l'élément sélectionné s'affiche à côté de la liste ; un serveur qui l'omet de la liste la fournit sur demande une fois la sélection immobile. Pas encore : la documentation à côté de la liste ne défile pas au clavier.
  • Show Callers (menu Edit et menu contextuel ; action showCallers) liste chaque endroit où la fonction sous le curseur est appelée, dans la liste qu'utilise Find References. Les références répondent à « où ce nom est-il écrit » ; ceci répond à « qu'est-ce qui l'exécute », et laisse de côté la déclaration, les imports et les mentions.
  • Go to Symbol in Project… (⌥⌘T, Ctrl+Alt+O sous Linux ; action projectSymbols) trouve une fonction, un type ou une constante n'importe où dans le projet par une partie de son nom, tels que les connaît le serveur du document au premier plan, et saute à sa déclaration. Sans rien de tapé, elle liste les symboles visités récemment dans le projet, le plus récent d'abord, sans interroger le serveur ; chacun est cherché par son nom là où il se trouve maintenant dans son fichier, si bien que des lignes écrites au-dessus depuis ne l'envoient pas au mauvais endroit. Un symbole renommé depuis apparaît à la ligne où il était.
  • Les types inférés et les noms des paramètres sont affichés. Les types qu'un serveur déduit — d'un let sans annotation, de l'argument d'une fermeture — et les noms des paramètres auxquels chaque argument est passé apparaissent estompés dans la ligne, là où le serveur les place : let d = make(); se lit let d: Drinker = make();, et pair(d, 2) se lit pair(who: d, n: 2). Ils ne font pas partie du texte : le curseur les enjambe, on ne peut ni les sélectionner ni les copier, et ils restent avec leur ligne pendant la frappe puis sont redemandés quand la frappe marque une pause. Settings ▸ General ▸ « Show the types the language server inferred » les désactive (editor.inlay_hints). Pas encore là : sur Linux les indications sont regroupées en fin de ligne, chaque type avec son nom, et les noms de paramètre sont laissés de côté, la vue de texte de GTK ne pouvant ouvrir un espace dans une ligne. Sans sélection, un curseur posé sur un nom marque les autres endroits où ce symbole est utilisé, tel que le serveur le connaît : une variable homonyme qui le masque est laissée tranquille. Sélectionner un mot marque toujours le même texte, serveur ou non. Les deux suivent le réglage de marquage des occurrences.
  • L'appel en cours de frappe dit ce qu'il prend. Une parenthèse ouvrante ou une virgule affiche la signature de la fonction au curseur, le paramètre en cours en gras ; la parenthèse fermante, ⎋ ou quitter la ligne la referme.
  • Laisser la souris sur un symbole affiche la documentation hover du serveur dans une bulle, avec le Markdown envoyé par les serveurs déjà rendu — blocs de code en chasse fixe, emphase et code en ligne stylés. Elle ne se déclenche que sur les identifiants (jamais sur les espaces ni les commentaires), reste ouverte tant que le pointeur se déplace dans le symbole qu'elle explique, et se désactive dans Présentation ▸ Documentation au survol (ou dans les Réglages). Réglages ▸ Général peut aussi exiger qu'une touche — ⇧, ⌃, ⌥ ou ⌘ — soit maintenue, pour que la documentation apparaisse quand on la demande et non là où le pointeur se pose ; presser la touche avec le pointeur déjà sur un symbole est aussi une demande. Afficher la documentation du symbole (⌃⌘H) la demande pour le symbole sous le curseur — même souris désactivée.
  • La bulle est utilisable. Elle reste tant que le pointeur est sur le texte qu'elle concerne et tant qu'il est dans la bulle, et se ferme un instant après qu'il a quitté les deux ; on peut donc l'atteindre. Son texte se sélectionne et se copie, et une documentation plus longue que la bulle défile depuis son début.
  • Aller à la définition (⌃⌘J, ou ⌘-clic) rejoint le symbole sous le curseur — d'un fichier à l'autre, en ouvrant ou en ramenant la cible au premier plan au besoin. Sur la définition il n'a nulle part où aller, alors il répond à la question qui reste : qui s'en sert. Un usage est un saut, plusieurs ouvrent la liste, et un symbole auquel rien ne renvoie le dit. Un serveur qui répond par plusieurs définitions — une déclaration et son implémentation — les propose de la même façon. Le raccourci de recherche des références ne change pas.
  • Aller à l'implémentation (⌥⌘B, Ctrl+F12 sous Linux) va d'une interface, d'un trait ou d'une méthode abstraite à ce qui l'implémente, et Aller à la définition du type (⇧⌘B, Ctrl+Maj+F12 sous Linux) d'une variable au type qu'elle contient. Une réponse est un saut ; plusieurs sont listées avec le code autour de chacune. Tous les serveurs ne répondent pas aux deux : celui qui ne le fait pas le dit près du curseur.
  • Chercher les références (⇧⌘R) liste chaque usage du symbole sous le curseur dans un panneau flottant — ↑/↓ pour se déplacer, ⏎ pour sauter. Le code d'abord, les tests ensuite, chacun sous un titre avec son compte : ce qui appelle ceci est la question, ce qui le vérifie est la suite. Quels fichiers sont des tests est une convention et non un fait — un répertoire tests, un parser_test.go, un Button.test.ts, un ParserTests.swift — donc la règle est prudente, et latest.rs n'est pas un test. Un #[cfg(test)] mod tests de Rust dans un fichier ordinaire est listé comme du code, ce que dit son chemin. Si tout tombe du même côté, il n'y a pas de titres.
  • Afficher les usages (⌥⌘F7, Ctrl+Alt+F7 sous Linux) est le coup d'œil rapide : les usages du symbole sous le curseur, sauf celui où se trouve le curseur, dans une liste près du curseur avec le code de l'usage sélectionné à côté sur macOS et dessous sous Linux. Un usage est un saut, et aucun le dit. Aller à la définition sur une définition montre la même liste, au même endroit. Chercher les références est la liste complète, déclaration comprise.
  • Formater le document (⌥⇧⌘F) demande d'abord au serveur puis retombe sur la chaîne de préprocesseurs de sauvegarde — le formatage marche donc sur les documents sans titre et les langages sans serveur, dès qu'une chaîne est configurée.
  • Une ligne marquée se lit. Poser le pointeur sur un soulignement montre ce qu'a dit le serveur, et Afficher le diagnostic de la ligne (⌃⌘E, Ctrl+Alt+E sous Linux) dit la même chose pour la ligne du curseur — le curseur est d'ordinaire en fin de ligne plutôt que dans la marque, c'est donc la ligne qui répond. Le message nomme sa gravité : un soulignement dit seulement que quelque chose ne va pas, et un avertissement ne doit pas se lire comme une erreur. Sans aller-retour : le diagnostic est déjà là.
  • Diagnostic suivant (F2, F8 sous Linux) et Diagnostic précédent (⇧F2, Maj+F8) mènent le curseur au début du diagnostic suivant ou précédent, en faisant le tour de la fin du document, et y affichent son message. Chacun est un saut : Revenir ramène là où était le curseur.
  • Diagnostics… (⇧⌘E, Ctrl+Shift+E sous Linux) liste tous les diagnostics du document dans l'ordre où ils apparaissent — celui dans lequel on les corrige et celui de la gouttière — avec la gravité sur chaque ligne. ⏎ y saute, et le saut entre dans la pile de retour.
  • Actions de code… (⌘., Ctrl+. sous Linux) demande ce que le serveur peut faire de l'endroit où est le curseur — importer ce nom, ajouter la branche manquante, ôter la variable inutilisée — et liste ce qui revient, la suggestion du serveur étant signalée comme telle. Les signalements sous le curseur accompagnent la requête tels que le serveur les a publiés, code et data compris : c'est ainsi qu'un serveur reconnaît ce qu'il a lui-même trouvé, et un signalement reconstruit ne lui dit rien. Une action que le serveur a envoyée sans sa modification lui est renvoyée pour qu'il la termine avant qu'elle soit appliquée, et une action qui porte une commande plutôt qu'une modification est exécutée par le serveur, qui renvoie la modification que fait la commande — c'est ainsi que fonctionne l'organisation des imports — et cette modification est appliquée comme celle d'une action. Une modification qui crée un fichier — un fichier de tests écrit par le serveur — le crée d'abord ; Annuler le retire tant qu'il est resté tel qu'il a été créé. Un serveur peut aussi demander qu'une page soit montrée : sa documentation, un listing en assembleur, le découpage d'un paquet — la page s'ouvre dans le navigateur — ou un fichier, qui s'ouvre dans l'éditeur à la ligne qu'il nomme. Les notes qu'un serveur attache à un point plutôt qu'à un passage, comme les détails d'optimisation du compilateur, sont soulignées sur le mot à cet endroit. Pas encore là : une modification qui renomme ou supprime un fichier le dit et laisse le fichier tel quel. Quand le curseur s'arrête sur une ligne où le serveur a quelque chose à proposer, une ampoule apparaît dans la marge à côté ; un clic sur l'ampoule liste ces mêmes actions. Passer à une autre ligne la retire.
  • Renommer le symbole (⌃⌘R) demande sur place : le nom sous le curseur devient un champ là où il se trouve, prérempli et sélectionné, ses autres usages dans le document étant marqués pendant ce temps. Entrée renomme dans tout l'espace de travail — les documents ouverts sont édités sur place, un pas d'annulation chacun, et les fichiers que personne n'a ouverts sont réécrits sur disque — et Échap, ou le clavier allant ailleurs, laisse tout comme c'était. Annuler dans n'importe quel fichier touché par le renommage le retire en entier ; voir Annuler et rétablir. Il en va de même pour une action de code qui modifie plusieurs fichiers.
  • Formater le document (⌥⇧⌘F) reformate via le serveur, en gardant les tabulations si le document indente avec des tabulations, des espaces sinon.
  • La couleur du serveur : les noms sont colorés selon ce que le serveur dit qu'ils sont — un paramètre, une constante, un type de la bibliothèque — par-dessus les couleurs de la grammaire ; voir Coloration syntaxique.
  • Plan du document (⇧⌘O) liste les symboles du fichier — l'imbrication rendue par l'indentation, filtrable en flou — et ⏎ saute vers la sélection.
  • Un serveur manquant est signalé une seule fois, avec la commande qui l'installe ; tout le reste de l'éditeur continue de fonctionner sans lui.

Pas encore là : Show Callers liste qui appelle une fonction, pas ce qu'elle appelle.

Serveurs

Textchum trouve les serveurs sur le PATH — il ne les installe pas :

Langage Serveur Installation
Rust rust-analyzer rustup component add rust-analyzer
Python pyright npm install -g pyright
Go gopls go install golang.org/x/tools/gopls@latest
C clangd Xcode CLT, ou brew install llvm
JavaScript typescript-language-server npm install -g typescript-language-server typescript
Swift sourcekit-lsp fourni avec la chaîne d'outils Xcode
Zig zls brew install zls
Bash bash-language-server npm install -g bash-language-server
C++ clangd Xcode CLT, or brew install llvm
TypeScript typescript-language-server npm install -g typescript-language-server typescript
Java jdtls brew install jdtls
C# csharp-ls dotnet tool install --global csharp-ls
Ruby ruby-lsp gem install ruby-lsp
Lua lua-language-server brew install lua-language-server
Haskell haskell-language-server ghcup install hls
OCaml ocamllsp opam install ocaml-lsp-server
Scala metals cs install metals
Nix nil nix profile install nixpkgs#nil
CMake cmake-language-server uv tool install cmake-language-server
JSON vscode-json-language-server npm install -g vscode-langservers-extracted
HTML vscode-html-language-server npm install -g vscode-langservers-extracted
CSS vscode-css-language-server npm install -g vscode-langservers-extracted
YAML yaml-language-server npm install -g yaml-language-server
TOML taplo brew install taplo
Markdown marksman brew install marksman
Elixir elixir-ls brew install elixir-ls
PHP intelephense npm install -g intelephense
Dockerfile docker-langserver npm install -g dockerfile-language-server-nodejs
XML lemminx brew install lemminx
SQL sqls go install github.com/sqls-server/sqls@latest
R languageserver R -e 'install.packages("languageserver")'

Les modèles Go sont servis par gopls également, le C++ par clangd, et TypeScript et TSX par les serveurs JavaScript. Plusieurs langages ont plus d'un serveur enregistré : Python dispose de pyright, basedpyright, pylsp, ruff, jedi, ty et pyrefly ; JavaScript, TypeScript et TSX de typescript-language-server, vtsls, deno et biome ; Ruby de ruby-lsp, solargraph et rubocop, ce dernier un linter à faire tourner à côté de l'un des deux autres ; PHP d'intelephense et phpactor.

Le tableau nomme celui qui sert quand la configuration ne dit rien ; les autres se demandent par identifiant, et lsp.servers accepte celui que l'éditeur ne connaît pas.

Choisir ses serveurs

Settings → Language Servers permet de décider quelle commande sert un langage — pour tous les projets (un défaut) ou pour une racine de projet précise. Les entrées de projet l'emportent sur les défauts ; les langages sans entrée utilisent le tableau ci-dessus. Les entrées vivent dans config.json sous "lsp", avec les garanties d'édition à la main habituelles du fichier :

{
  "lsp": {
    "defaults": {"python": "pylsp"},
    "projects": {"/work/projA": {"python": "pyright-langserver --stdio"}}
  }
}

Le champ de langage propose les langages que cette compilation connaît et accepte toujours n'importe quel texte : un langage peut être configuré avant qu'une grammaire existe pour lui, et l'entrée sert encore quand elle arrive.

Plus d'un serveur pour un langage

Un langage, c'est souvent un vérificateur de types et un linter, et chacun est un serveur à part entière. Une entrée les liste avec ;; entre eux :

{ "lsp": { "defaults": { "python": "pyright ;; ruff" } } }

Tous reçoivent le document et ses modifications, et leurs résultats s'affichent ensemble. Une requête — hover, complétion, aller à la définition, formatage — va au premier serveur listé qui dit la fournir ; l'ordre est donc l'ordre de préférence. Une action de code est demandée au serveur qui a signalé le résultat sous le curseur. Si l'un d'eux n'est pas installé, les autres tournent quand même.

Pas encore là : la complétion vient d'un seul serveur, elle n'est pas fusionnée entre tous.

Définir un serveur que l'éditeur ne connaît pas

lsp.servers contient des entrées de la même forme que la table intégrée : un serveur peut donc être ajouté sans changement de code, et un serveur déjà connu redéfini en réutilisant son identifiant :

{
  "lsp": {
    "servers": {
      "basedpyright": {
        "command": "{project}/.venv/bin/basedpyright-langserver",
        "args": ["--stdio"],
        "languages": ["python"],
        "install": "uv tool install basedpyright"
      }
    },
    "defaults": {"python": "basedpyright"}
  }
}

command est obligatoire ; le reste peut être omis. La table intégrée reste disponible à côté de ces entrées : une configuration muette a donc quand même des serveurs, et une version qui en apprend un nouveau le propose sans réécriture de la configuration. Définir un serveur ne change pas celui qu'un langage utilise par défaut ; c'est lsp.defaults qui en décide.

Réglages d'un serveur

Les serveurs prennent des réglages — quels lints tournent, quelle sévérité pour le vérificateur de types — et lsp.settings est leur place : un objet par id de serveur, avec les clés par section comme le serveur les demande.

{
  "lsp": {
    "settings": {
      "rust-analyzer": {"rust-analyzer": {"check": {"command": "clippy"}}},
      "gopls": {"gopls": {"staticcheck": true}},
      "pyright": {"python": {"analysis": {"typeCheckingMode": "strict"}}}
    },
    "init_options": {
      "typescript-language-server": {"preferences": {"quotePreference": "single"}}
    }
  }
}

L'objet est envoyé au serveur une fois démarré et distribué par section quand le serveur le demande, ce qui à eux deux couvre la façon dont les serveurs lisent leurs réglages. C'est la forme de la table settings d'une configuration nvim-lspconfig, que l'on peut donc reprendre telle quelle. lsp.init_options contient ce qu'un serveur lit dans initializationOptions. Une ligne de commande tapée à la main n'a pas d'id ; ses réglages sont cherchés sous le nom de la commande lancée.

Un serveur lit ses réglages au démarrage. Appliquer un profil de langage redémarre les serveurs ; après une modification à la main, Settings ▸ Language Servers a le bouton qui le fait.

Settings ▸ Language Servers les modifie sous Server settings : un objet JSON par serveur, pour tous les projets ou pour une racine. L'objet d'un projet se trouve sous lsp.project_settings.<racine>.<serveur> et, comme toute entrée par projet, remplace celui par défaut au lieu de s'y ajouter. Un texte qui n'est pas un objet JSON n'est pas enregistré, et le champ le dit.

Pas encore là : lsp.init_options se modifie à la main, et n'est pas par projet.

Nommer un serveur, et en désigner un dans le projet

L'entrée d'un langage accepte l'identifiant d'un serveur connu de l'éditeur ou une ligne de commande.

Un identifiant apporte avec lui les arguments du serveur. Un langage qui a plusieurs serveurs enregistrés utilise le premier tant que la configuration n'en nomme pas un autre.

Une ligne de commande est exécutée telle quelle, avec deux substitutions :

  • {project} — la racine du projet sur laquelle l'instance est indexée.
  • {home} — le répertoire personnel de l'utilisateur.
{"lsp": {"defaults":
  {"python": "{project}/.venv/bin/basedpyright-langserver --stdio"}}}

C'est ce qu'il faut à un dépôt qui embarque ses propres outils : un environnement virtuel, une entrée de node_modules/.bin, un serveur inclus dans le dépôt. La substitution se fait argument par argument après le découpage de la ligne : un chemin de projet contenant des espaces reste un seul argument.

La commande d'une entrée s'édite sur place — corriger une coquille ou ajouter un --stdio manquant se fait dans la ligne elle-même, avec ⏎ ou en cliquant ailleurs, sans supprimer puis recréer. Les changements s'appliquent aux serveurs démarrés ensuite ; le bouton Restart Servers Now de l'onglet retire les instances en cours et les relance sous la nouvelle configuration.

Quand il n'y a pas de serveur

Deux filets de sécurité couvrent le cas sans serveur :

  • Le repli ctags. Avec Ctags fallback activé dans Réglages → Projects (par défaut ou par projet, comme chaque drapeau de projet), Aller à la Définition est répondu depuis un index Universal Ctags du projet dès qu'aucun serveur de langage n'est disponible — et aussi quand un serveur en marche n'a pas de réponse. L'index se construit au premier usage et se rafraîchit au fil des sauts ; ctags connaît des noms, pas la sémantique : c'est un repli, pas un remplacement. Il faut Universal Ctags (brew install universal-ctags) : le ctags livré par macOS dans /usr/bin est un autre programme, bien plus ancien, incapable de produire l'index JSON que ceci lit. Textchum regarde au-delà de celui-ci pour trouver un vrai Universal Ctags plus loin dans le PATH.
  • Le journal de débogage. Chaque décision sur le chemin de « fichier ouvert » à « serveur en marche » — la racine de projet résolue, quel serveur a été choisi et pourquoi, les échecs de lancement avec le PATH exact consulté et chaque transition d'état — est ajoutée à :
~/Library/Logs/Textchum/lsp.log

La sortie d'erreur propre à chaque serveur (stderr) y est capturée aussi : un serveur qui sort pendant le démarrage laisse sa plainte au dossier — une commande privée de son option de transport (le --stdio de pyright, par exemple) se diagnostique d'un coup d'œil, et le journal signale explicitement quand une commande personnalisée omet des arguments que le registre intégré sait requis. Quand un projet se retrouve mystérieusement sans support de langage, ce fichier nomme la pièce manquante.

Une cause classique mérite sa note : les applications lancées depuis le Finder héritaient du PATH minimal de macOS, qui ne contient aucun des endroits où vivent réellement les serveurs de langage (Homebrew, npm, cargo, go). Textchum adopte désormais au démarrage le PATH du shell de connexion — plus quelques répertoires d'outils conventionnels — si bien qu'un serveur qui fonctionne depuis le terminal fonctionne aussi depuis le Dock.

Sous le capot

Le client vit dans le noyau, derrière la même frontière que tout le reste : JSON-RPC sur stdio, une poignée de main d'initialisation avant tout trafic de documents, et une synchronisation en document complet (la synchronisation incrémentale est une optimisation ultérieure). Les messages du serveur sont traités hors du fil d'interface et la rejoignent par l'unique canal d'événements du noyau ; un processus serveur bloqué reçoit un délai de grâce borné à la fermeture puis est tué, si bien que quitter Textchum ne peut jamais rester suspendu à un serveur défaillant. Tout le chemin du protocole est exercé en CI contre un serveur scripté.

Les instances prennent aussi soin d'elles-mêmes : un serveur qui plante en cours de session est relancé automatiquement avec recul (1 → 2 → 4 → 8 secondes ; quatre échecs d'affilée et il reste à terre jusqu'à un redémarrage ou un changement de configuration), et une instance dont aucun document ouvert n'a eu besoin depuis cinq minutes est arrêtée — l'ouverture suivante en lance une fraîche.

  • Les snippets de la complétion se déplient et se parcourent. Le premier marqueur revient sélectionné, donc taper le remplace ; ⇥ passe au suivant et ⇧⇥ au précédent ; un marqueur écrit plusieurs fois recopie celui qu'on tape. Arriver au bout, appuyer sur ⎋ ou cliquer ailleurs rend les touches.
  • Présentation ▸ État des serveurs liste les instances en cours et les transitions récentes de la session, rafraîchi en direct, avec un pointeur vers le journal complet.