Sprogintelligens (diagnostik, oplysninger ved pegning, signaturhjælp, symboler)¶
En lille, backend-neutral familie af kontrakter til sprogintelligens — de data, en
leverandør sender ind i editorens flader for kodeintelligens. Den følger projektets
regel om, at udvidelsesmoduler bidrager med data, mens kernen tegner:
bølgelinjerne, boblen ved pegning og markørspringene hører hjemme i editorvisningen og
blotlægges aldrig for udvidelsesmoduler; et modul leverer kun POD- og
std::string-data, så den samme kontrakt senere kan opfyldes af en Python-backend.
Den første forbruger er LSP-udvidelsesmodulet, men kanalerne er almene — en statisk analysator eller en stavekontrol kan udgive diagnostik på samme måde.
Hvad der ikke er her
Gå til definition, find referencer, omdøb og formatér kræver ingen API for sig
selv — de navigerer eller redigerer kun tekst, hvilket IEditorService allerede
udtrykker (caret, openFile, setCaret, setText), så de bliver ved med at
være kommandoer inde i modulet. Fuldførelse genbruger den eksisterende
ICompletionRegistry. En ny kontrakt tilføjes kun, når der findes en flade i
kernen, der tegner eller vælger, og potentielt flere leverandører — og det
er tilfældet for diagnostik, oplysninger ved pegning, signaturhjælp og symboler.
Diagnostik — skub¶
En leverandør skubber diagnostik, når den har noget (den afgør selv, hvornår
resultaterne er klar); editoren tegner den som bølgede understregninger. Modtageren nås
via IPluginContext::diagnostics(). Værdityperne er almindelig 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;
};
sourceer en erstatningsnøgle pr.(dokument, kilde). At udgive for en kilde erstatter den kildes tidligere diagnostik på dokumentet, så flere producenter (LSP, stavekontrol, byggelog) kan sameksistere uden at slette hinandens.- Udgiv en tom tabel for en kilde for at trække dens diagnostik tilbage.
- Alvorsgraderne tegnes med hver sin farve (fejl, advarsel, info, tip); editoren gør også meget korte områder bredere, så en markør på ét enkelt tegn bliver ved med at være synlig.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
Værten stiller altid en gyldig modtager til rådighed (en tom udgave, når editoren ikke
har nogen flade til diagnostik), så du aldrig behøver at kontrollere, om
diagnostics() er null.
Oplysninger ved pegning — asynkront træk¶
Oplysninger ved pegning drives af editoren (musen hviler over et symbol). Et
udvidelsesmodul registrerer en leverandør, der svarer asynkront; registret nås
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;
}
registerProvidergiver etHoverTokentilbage (RAII); ødelæg det ishutdown()for at afregistrere.- Hver leverandør registrerer sig med en prioritet. Vælg det bånd, der
beskriver, hvad du svarer, ikke hvor vigtigt det føles:
kFallbacktil oplysninger på ord- eller tekstniveau (en ordbogsdefinition, målet for en URL),kLanguagetil sprogoplysninger om symbolet (en sprogservers hover, en linters regel),kActiveSessiontil oplysninger, der skal vinde, så længe de er aktive (værdien under markøren i en debugger). Værdier mellem båndene (kLanguage + 10) er tilladt, når du skal ordne dig i forhold til en kendt nabo. - Når musen hviler, spørger værten alle leverandører på én gang og viser det ikke-tomme svar fra leverandøren med højest prioritet; ved lige prioritet vinder den leverandør, der blev registreret først. Et svar med lavere prioritet, der kommer først, holdes tilbage, indtil hver højere leverandør har svaret tomt. Svarer alle tomt, vises intet.
- En langsom leverandør kan ikke holde værktøjstippet tilbage for evigt:
300 ms efter forespørgslen vises det bedste svar, der er modtaget indtil
da, og senere svar på den forespørgsel kasseres. En
kActiveSession-leverandør skal derfor svare tomt med det samme, når den intet har at sige (ingen session) — ellers forsinker den hver hover i editoren med op til tidsgrænsen. - Svar, der kommer, efter at brugeren er gået videre, kasseres.
- Svaret er almindelig opmærkningstekst og gengives som Markdown i en boble, så overskrifter, fed skrift og kodeblokke virker — hold kontrakten til streng ind, streng ud.
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);
Signaturhjælp — asynkront træk¶
Som oplysninger ved pegning, men drevet af, at man skriver et kalds ( eller et ,.
En leverandør svarer med den aktive signatur; værten tegner den som et kaldetip med
den aktive parameter fremhævet. Registret nås 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; };
Værten viser det første ikke-tomme svar; [activeStart, activeEnd) er den aktive
parameters byteområde inde i signature og bruges til fremhævningen.
Symboler — asynkront træk (gå til symbol)¶
En leverandør opregner et dokuments symboler (og søger i arbejdsområdet); værten tegner
vælgeren (kommandopalettens tilstande Ctrl+Shift+O / Ctrl+T), og ethvert
udvidelsesmodul kan trække de samlede symboler — det gør Funktionsliste-panelet,
hvilket gør dette til en ægte kontrakt med flere forbrugere. Den nås 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;
};
- Placeringer udtrykkes, som værten navigerer: en
pathi filsystemet (tom betyder forespørgslens dokument) plus en 0-baseretlineog encolumni byte inde i den — der skal ikke omregnes yderligere på kaldstedet. - Værten leverer den første ikke-tomme symbolliste eller en tom liste, når hver leverandør har svaret tomt (så en ventende vælger altid får noget at vide).
- Forbrugersiden:
requestDocumentSymbols/requestWorkspaceSymbolspå registret er trækmodstykket tilregisterProvider— et udvidelsesmodul kalder dem for at læse det, andre moduler (f.eks. en sprogserver) bidrager med. Svaret udløses én gang, på brugerfladetråden, muligvis senere (asynkront). Funktionsliste-panelet bruger dette som spørg og fald tilbage: det viser sin egen regex-oversigt med det samme og opgraderer til svaret, hvis der kommer et ikke-tomt; et tomt svar rydder aldrig udgangspunktet. Gardér et sent svar mod nedrivning og mod en nyere forespørgsel (dokument-id plus generation).
Sprog — opregn, skift, bidrag¶
Syntaksfarvning er en kernefunktion: værten ejer et katalog af farvelæggere (de
medleverede colorizers/*.json-filer plus brugerens egne), afgør et dokuments sprog ud
fra dets sti og første linje og farver visningen uden noget udvidelsesmodul.
ctx.languages() (ILanguageRegistry) er den måde, et modul er med på:
// 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)
Et bidraget sprog opfører sig præcis som et medleveret: værten slår navnene op i
backend-lexerens metadata, og det aktive tema leverer farverne. At registrere et id,
der allerede findes, erstatter det, så længe symbolet lever; når symbolet dør,
kommer den fortrængte definition tilbage, og åbne dokumenter, der bruger id'et, farves
igen — så et udvidelsesmodul kan tilsidesætte et medleveret sprog uden at ødelægge
kataloget varigt. Bidrag overlever også den genindlæsning af kataloget, som et
Indstillinger ▸ Anvend eller et temaskift udløser. Værten kan ikke få nye
backend-lexere under kørslen — lexer skal navngive en, der findes. Altid gyldigt:
en vært uden et farvelægningslag leverer en tom udgave, der opregner ingenting og
nægter enhver ændring.
Status¶
Alle fire kontrakter (diagnostik, oplysninger ved pegning, signaturhjælp, symboler)
blev tilføjet inden udgivelsen, uden at MTE_PLUGIN_API_VERSION blev hævet;
sprogfladen (ILanguageRegistry) fulgte på samme måde. Deres første forbruger er
LSP-udvidelsesmodulet; symbolkontrakten har med vilje flere
forbrugere (kommandopaletten i dag, Funktionsliste-panelet som det næste).