Intelligence de langage (diagnostics, hover, aide à la signature, symboles)¶
Une petite famille de contrats, neutre vis-à-vis du backend, pour
l'intelligence de langage — les données qu'un fournisseur alimente vers les
surfaces d'intelligence de code de l'éditeur. Elle suit la règle du projet
« les plugins fournissent des données, le cœur fait le rendu » : les
soulignements ondulés, la popup de survol et les sauts de caret vivent dans la
vue de l'éditeur et ne sont jamais exposés aux plugins ; un plugin ne fournit
que des données POD/std::string, de sorte que le même contrat pourra plus
tard être rempli par un backend Python.
Le premier consommateur est le plugin LSP, mais les canaux sont génériques — un linter ou un correcteur orthographique peut publier des diagnostics de la même manière.
Ce qui ne se trouve pas ici
Aller à la définition, rechercher les références, renommer et formater
n'ont besoin d'aucune API propre — ils ne font que naviguer ou éditer
du texte, ce que IEditorService exprime déjà (caret, openFile,
setCaret, setText) ; ils restent donc des commandes internes au
plugin. La complétion réutilise l'ICompletionRegistry existant. Un
nouveau contrat n'est ajouté que lorsqu'il existe une surface de
rendu/sélection dans le cœur et potentiellement plusieurs
fournisseurs — ce qui est le cas pour les diagnostics, le survol, l'aide
à la signature et les symboles.
Diagnostics — push¶
Un fournisseur pousse des diagnostics dès qu'il en dispose (c'est lui qui
décide quand les résultats sont prêts) ; l'éditeur les rend sous forme de
soulignements ondulés. Atteignez le récepteur via
IPluginContext::diagnostics(). Les types valeur sont de simples POD
(PluginApi/DiagnosticTypes.h) :
enum class DiagnosticSeverity { Error, Warning, Information, Hint };
struct Diagnostic {
MatchRange range; // UTF-8 byte range in the document
DiagnosticSeverity severity = DiagnosticSeverity::Error;
std::string message;
std::string code;
};
struct IDiagnosticsSink { // PluginApi/IDiagnosticsSink.h
virtual void publishDiagnostics(DocumentId doc, std::string_view source,
const std::vector<Diagnostic>& diags) = 0;
virtual void clearDiagnostics(DocumentId doc) = 0;
};
sourceest une clé de remplacement par couple(doc, source). Publier pour une source remplace les diagnostics précédents de cette source sur le document, si bien que plusieurs producteurs (LSP, correction orthographique, log de compilation) coexistent sans s'écraser mutuellement.- Publiez un vecteur vide pour une source afin de retirer ses diagnostics.
- Les sévérités sont rendues avec des couleurs distinctes (error/warning/info/hint) ; l'éditeur élargit aussi les plages très courtes pour qu'un marqueur d'un seul caractère reste visible.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
L'hôte fournit toujours un récepteur valide (un no-op quand l'éditeur n'a pas
de surface de diagnostics), vous n'avez donc jamais besoin de vérifier la
nullité de diagnostics().
Hover — pull asynchrone¶
Le survol (hover) est piloté par l'éditeur (pause de la souris sur un
symbole). Un plugin enregistre un fournisseur qui répond de manière
asynchrone ; atteignez le registre via IPluginContext::hover()
(PluginApi/IHoverRegistry.h) :
struct HoverRequest { DocumentId doc; std::int64_t position; }; // byte offset
using HoverReply = std::function<void(std::string markup)>; // empty = nothing
struct IHoverProvider {
virtual void requestHover(const HoverRequest&, HoverReply reply) = 0;
};
struct IHoverRegistry {
virtual HoverToken registerProvider(IHoverProvider& provider) = 0;
};
registerProviderrenvoie unHoverToken(RAII) ; détruisez-le dansshutdown()pour vous désenregistrer.- Lors de la pause de la souris, l'hôte interroge chaque fournisseur enregistré et affiche la première réponse non vide ; les réponses qui arrivent après que l'utilisateur est passé à autre chose sont abandonnées.
- La réponse est un simple texte de balisage rendu comme Markdown dans une popup, donc les titres, le gras et les blocs de code fonctionnent — gardez le contrat chaîne-en-entrée / chaîne-en-sortie.
class MyHover final : public IHoverProvider {
public:
void requestHover(const HoverRequest& r, HoverReply reply) override {
// ...look up info for r.doc at byte offset r.position, possibly async...
reply("**foo** — a thing\n\n```cpp\nint foo();\n```");
}
};
// in initialize(): m_token = ctx.hover().registerProvider(m_hover);
Aide à la signature — pull asynchrone¶
Comme le survol, mais déclenché par la frappe du ( d'un appel ou d'une ,.
Un fournisseur répond avec la signature active ; l'hôte la rend sous forme
d'info-bulle d'appel (call tip) avec le paramètre actif mis en évidence.
Atteignez le registre via IPluginContext::signatureHelp()
(PluginApi/ISignatureHelpRegistry.h) :
struct SignatureHelpRequest { DocumentId doc; std::int64_t position; std::string languageId; };
struct SignatureHelp { std::string signature; std::int32_t activeStart, activeEnd; }; // byte range in `signature`
using SignatureHelpReply = std::function<void(SignatureHelp)>; // empty signature = nothing
struct ISignatureHelpProvider {
virtual void requestSignatureHelp(const SignatureHelpRequest&, SignatureHelpReply) = 0;
};
struct ISignatureHelpRegistry { virtual SignatureHelpToken registerProvider(ISignatureHelpProvider&) = 0; };
L'hôte affiche la première réponse non vide ; [activeStart, activeEnd)
est la plage d'octets du paramètre actif au sein de signature, utilisée
pour la mise en évidence.
Symboles — pull asynchrone (aller au symbole)¶
Un fournisseur énumère les symboles d'un document (et effectue la recherche
dans l'espace de travail) ; l'hôte rend le sélecteur (les modes
Ctrl+Shift+O / Ctrl+T de la palette de commandes) et tout plugin peut
tirer (pull) les symboles agrégés — le panneau Function List le fait, ce
qui en fait un contrat véritablement multi-consommateurs.
Atteignez-le via IPluginContext::symbols() (PluginApi/ISymbolRegistry.h) :
struct SymbolLocation { std::string path; std::int32_t line, column; }; // path empty = request doc; column = byte offset in line
struct SymbolInfo { std::string name; std::int32_t kind; std::string containerName; SymbolLocation location; }; // kind = LSP SymbolKind (1..26)
using SymbolReply = std::function<void(std::vector<SymbolInfo>)>;
struct ISymbolProvider {
virtual void requestDocumentSymbols(const DocumentSymbolRequest&, SymbolReply) = 0; // { DocumentId doc; std::string languageId; }
virtual void requestWorkspaceSymbols(const WorkspaceSymbolRequest&, SymbolReply) = 0; // { std::string query; }
};
struct ISymbolRegistry {
virtual SymbolToken registerProvider(ISymbolProvider&) = 0; // producer side
// consumer side (pull) — used by the Command Palette and the Function List dock:
virtual void requestDocumentSymbols(const DocumentSymbolRequest&, SymbolReply) const = 0;
virtual void requestWorkspaceSymbols(const WorkspaceSymbolRequest&, SymbolReply) const = 0;
};
- Les emplacements sont exprimés tels que l'hôte navigue : un
pathdu système de fichiers (vide signifie le document de la requête) plus unelineen base 0 et unecolumnen octets au sein de celle-ci — aucune conversion supplémentaire n'est nécessaire au point d'appel. - L'hôte délivre la première liste de symboles non vide, ou une liste vide une fois que tous les fournisseurs ont répondu vide (ainsi un sélecteur en attente reçoit toujours une réponse).
- Côté consommateur :
requestDocumentSymbols/requestWorkspaceSymbolssur le registre sont le pendant « pull » deregisterProvider— un plugin les appelle pour lire ce que d'autres plugins (p. ex. un serveur de langage) contribuent. La réponse se déclenche une fois, sur le thread UI, éventuellement plus tard (asynchrone). Le panneau Function List l'utilise selon le schéma demander-et-se-replier : il affiche instantanément son propre plan basé sur des regex et, si une réponse non vide arrive, s'y met à niveau ; une réponse vide n'efface jamais la base. Protégez une réponse tardive contre le démontage et contre une requête plus récente (id de document + génération).
Statut¶
Les quatre contrats (diagnostics, survol, aide à la signature, symboles) ont
été ajoutés avant la première version sans incrémenter
MTE_PLUGIN_API_VERSION. Leur premier consommateur est le
plugin LSP ; le contrat de symboles est
volontairement multi-consommateurs (la palette de commandes aujourd'hui, le
panneau Function List ensuite).