Taalintelligentie (diagnostiek, aanwijshulp, handtekeninghulp, symbolen)¶
Een kleine, backend-neutrale familie contracten voor taalintelligentie — de
gegevens die een leverancier aan de code-intelligentievlakken van de editor
voert. Zij volgt de regel van het project «plug-ins dragen gegevens bij, de kern
tekent»: de golflijnen, de pop-up bij aanwijzen en de cursorsprongen wonen in de
editorweergave en worden nooit aan plug-ins blootgesteld; een plug-in levert
alleen POD- en std::string-gegevens, zodat hetzelfde contract later door een
Python-backend vervuld kan worden.
De eerste afnemer is de LSP-plug-in, maar de kanalen zijn algemeen: een statische analysator of een spellingcontrole kan diagnostiek op dezelfde manier publiceren.
Wat hier niet staat
Ga naar definitie, verwijzingen zoeken, naam wijzigen en opmaken hebben
geen eigen API nodig — ze navigeren of bewerken alleen tekst, wat
IEditorService al uitdrukt (caret, openFile, setCaret, setText), dus
ze blijven opdrachten binnen de plug-in. Aanvulling hergebruikt de bestaande
ICompletionRegistry. Er wordt pas een nieuw contract toegevoegd wanneer er
een teken- of kiesvlak in de kern is en er mogelijk meerdere
leveranciers zijn — wat het geval is voor diagnostiek, aanwijshulp,
handtekeninghulp en symbolen.
Diagnostiek — duwen¶
Een leverancier duwt diagnostiek zodra hij die heeft (hij beslist wanneer de
resultaten klaar zijn); de editor tekent ze als golvende onderstrepingen. Bereik
de opvang via IPluginContext::diagnostics(). De waardetypen zijn eenvoudige 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;
};
sourceis een vervangsleutel per(doc, source). Publiceren voor een bron vervangt de vorige diagnostiek van die bron op het document, zodat meerdere producenten (LSP, spellingcontrole, bouwlogboek) naast elkaar bestaan zonder elkaar te overschrijven.- Publiceer een lege vector voor een bron om haar diagnostiek in te trekken.
- Ernstgraden worden met verschillende kleuren getekend (fout/waarschuwing/informatie/hint); de editor verbreedt bovendien heel korte bereiken, zodat een markering van één teken zichtbaar blijft.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
De host levert altijd een geldige opvang (een niet-bewerking wanneer de editor
geen diagnostiekvlak heeft), dus u hoeft nooit te controleren of diagnostics()
niet nul is.
Aanwijzen — asynchroon trekken¶
Aanwijshulp wordt door de editor aangestuurd (de muis die op een symbool
blijft rusten). Een plug-in registreert een leverancier die asynchroon
antwoordt; bereik het register 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,
std::int32_t priority) = 0;
};
namespace hover_priority {
inline constexpr std::int32_t kFallback = 0;
inline constexpr std::int32_t kLanguage = 100;
inline constexpr std::int32_t kActiveSession = 1000;
}
registerProviderlevert eenHoverToken(RAII) op; vernietig hem inshutdown()om de registratie op te heffen.- Elke leverancier registreert zich met een prioriteit. Kies de band die
beschrijft wat je beantwoordt, niet hoe belangrijk het aanvoelt:
kFallbackvoor informatie op woord- of tekstniveau (een woordenboekdefinitie, het doel van een URL),kLanguagevoor taalkundige informatie over het symbool (de hover van een taalserver, de regel van een linter),kActiveSessionvoor informatie die moet winnen zolang ze actief is (de waarde onder de aanwijzer in een debugger). Waarden tussen de banden (kLanguage + 10) zijn toegestaan wanneer je je ten opzichte van een bekende buur moet ordenen. - Blijft de aanwijzer rusten, dan vraagt de host alle leveranciers tegelijk en toont hij het niet-lege antwoord van de leverancier met de hoogste prioriteit; bij gelijke prioriteit wint de leverancier die het eerst is geregistreerd. Een antwoord met lagere prioriteit dat eerst binnenkomt, wordt vastgehouden tot elke hogere leverancier leeg heeft geantwoord. Antwoorden ze allemaal leeg, dan wordt er niets getoond.
- Een trage leverancier kan de tooltip niet eindeloos ophouden: 300 ms na
het verzoek wordt het beste tot dan ontvangen antwoord getoond en worden
latere antwoorden op dat verzoek verworpen. Een
kActiveSession-leverancier moet daarom meteen leeg antwoorden als hij niets te zeggen heeft (geen sessie) — anders vertraagt hij elke hover in de editor met maximaal die tijdslimiet. - Antwoorden die binnenkomen nadat de gebruiker verder is gegaan, worden verworpen.
- Het antwoord is gewone opmaaktekst en wordt als Markdown in een pop-up weergegeven, dus koppen, vet en codeblokken werken — houd het contract op tekenreeks in / tekenreeks uit.
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, hover_priority::kLanguage);
Handtekeninghulp — asynchroon trekken¶
Als aanwijshulp, maar aangestuurd door het typen van de ( of een , van een
aanroep. Een leverancier antwoordt met de actieve handtekening; de host tekent die
als een aanroephulp met de actieve parameter gemarkeerd. Bereik het register
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; };
De host toont het eerste niet-lege antwoord; [activeStart, activeEnd) is het
bytebereik van de actieve parameter binnen signature, gebruikt voor de
markering.
Symbolen — asynchroon trekken (ga naar symbool)¶
Een leverancier somt de symbolen van een document op (en doorzoekt de werkruimte);
de host tekent de kiezer (de symboolmodi Ctrl+Shift+O / Ctrl+T van het
Opdrachtenpalet) en elke plug-in kan de verzamelde symbolen trekken — het dok
Functielijst doet dat, wat hier een werkelijk contract met meerdere afnemers van
maakt.
Bereik het 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;
};
- Locaties worden uitgedrukt zoals de host navigeert: een
pathin het bestandssysteem (leeg betekent het document van het verzoek) plus een vanaf 0 geteldelineen eencolumnin bytes daarin — er is op de plek van de aanroep geen verdere omzetting nodig. - De host levert de eerste niet-lege symbolenlijst, of een lege lijst zodra elke leverancier leeg geantwoord heeft (zodat een wachtende kiezer altijd iets te horen krijgt).
- Afnemerskant:
requestDocumentSymbols/requestWorkspaceSymbolsop het register zijn de trek-tegenhanger vanregisterProvider— een plug-in roept ze aan om te lezen wat andere plug-ins (bijvoorbeeld een taalserver) bijdragen. Het antwoord komt één keer, op de interfacethread, mogelijk later (asynchroon). Het dok Functielijst gebruikt dit patroon van vragen en terugvallen: het toont meteen zijn eigen schets op basis van reguliere expressies en schakelt over zodra er een niet-leeg antwoord binnenkomt; een leeg antwoord wist de basis nooit. Bescherm een laat antwoord tegen het afbreken en tegen een nieuwer verzoek (document-id + generatie).
Talen — opsommen, wisselen, bijdragen¶
Syntaxiskleuring is een functie van de kern: de host bezit een catalogus van
colorizers (de meegeleverde bestanden colorizers/*.json plus die van de
gebruiker), herkent de taal van een document aan zijn pad en eerste regel, en
kleurt de weergave zonder enige plug-in. ctx.languages() (ILanguageRegistry)
is hoe een plug-in meedoet:
// Enumeration -- e.g. to fill a per-language settings dropdown.
std::vector<LanguageInfo> langs = ctx.languages().availableLanguages(); // {id, name}, by name
// Per-document state. languageFor() reports the same id as
// IEditorService::colorizerId(); setLanguageFor() takes exactly the path
// the Language menu takes ("" clears colouring; false = unknown id / doc).
std::string id = ctx.languages().languageFor(doc);
bool ok = ctx.languages().setLanguageFor(doc, "json");
bool redone = ctx.languages().autoDetectFor(doc); // undo a manual override
// Contribution: a whole language definition, in the same shape as a
// shipped colorizer file -- backend lexer NAME, style NAMES -> category,
// keyword groups by the lexer's own slot description. Never engine ids.
LanguageDefinition def;
def.id = "mylang"; def.name = "My Language"; def.lexer = "cpp";
def.extensions = {"mylang"};
def.keywords = {{"Primary keywords and identifiers", "if else while"}};
def.styles = {{"SCE_C_COMMENT", SyntaxCategory::Comment},
{"SCE_C_STRING", SyntaxCategory::String}};
m_lang = ctx.languages().registerLanguage(def); // LanguageToken (RAII)
Een bijgedragen taal gedraagt zich precies als een meegeleverde: de host lost de
namen op tegen de metagegevens van de lexer van de backend en het actieve thema
levert de kleuren. Een id registreren die al bestaat vervangt die zolang het
token leeft; sterft het token, dan komt de verdrongen definitie terug en worden
geopende documenten die de id gebruiken opnieuw gekleurd — zodat een plug-in een
meegeleverde taal kan overschrijven zonder de catalogus blijvend te beschadigen.
Bijdragen overleven ook de herlading van de catalogus die een Voorkeuren ▸
Toepassen of een themawissel in gang zet. De host kan tijdens het draaien geen
nieuwe lexers van de backend krijgen — lexer moet er een noemen die bestaat.
Altijd geldig: een host zonder kleurlaag levert een niet-bewerking die niets
opsomt en elke wijziging weigert.
Status¶
Alle vier de contracten (diagnostiek, aanwijshulp, handtekeninghulp, symbolen)
zijn vóór de uitgave toegevoegd zonder MTE_PLUGIN_API_VERSION te verhogen; het
taalvlak (ILanguageRegistry) is op dezelfde manier gevolgd. Hun eerste afnemer
is de LSP-plug-in; het symboolcontract is met opzet
voor meerdere afnemers (vandaag het Opdrachtenpalet, daarna het dok Functielijst).