Jazyková inteligencia (diagnostika, informácie pri najazdení, pomoc so signatúrou, symboly)¶
Malá rodina kontraktov nezávislých od backendu pre jazykovú inteligenciu — údaje, ktoré
poskytovateľ posiela plochám inteligencie kódu v editore. Riadi sa pravidlom projektu
„zásuvné moduly prispievajú údajmi, jadro vykresľuje“: vlnovky, vyskakovacie okno pri
najazdení aj skoky kurzora žijú v zobrazení editora a zásuvným modulom sa nikdy nesprístupňujú;
modul dodáva iba údaje typu POD a std::string, takže ten istý kontrakt môže neskôr naplniť
backend v Pythone.
Prvým konzumentom je zásuvný modul LSP, no kanály sú všeobecné — rovnako môže diagnostiku zverejňovať statický analyzátor alebo kontrola pravopisu.
Čo tu nie je
Prechod na definíciu, hľadanie odkazov, premenovanie a formátovanie nepotrebujú vlastné
API — iba navigujú alebo upravujú text, čo IEditorService už vyjadruje (caret,
openFile, setCaret, setText), takže ostávajú príkazmi vnútri zásuvného modulu.
Dopĺňanie znova využíva existujúci ICompletionRegistry. Nový kontrakt sa pridáva iba
vtedy, keď existuje plocha jadra na vykreslenie či výber a potenciálne viacero
poskytovateľov — čo je prípad diagnostiky, informácií pri najazdení, pomoci so signatúrou
a symbolov.
Diagnostika — push¶
Poskytovateľ diagnostiku posiela, kedykoľvek ju má (sám rozhoduje, kedy sú výsledky
pripravené); editor ju vykreslí ako vlnovkové podčiarknutia. K cieľu sa dostanete cez
IPluginContext::diagnostics(). Hodnotové typy sú obyčajné 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;
};
sourceje nahrádzací kľúč pre dvojicu(dokument, zdroj). Zverejnenie pre daný zdroj nahradí jeho predchádzajúcu diagnostiku v dokumente, takže viacerí producenti (LSP, kontrola pravopisu, log zostavenia) môžu existovať vedľa seba bez toho, aby si prekážali.- Zverejnením prázdneho vektora pre daný zdroj jeho diagnostiku stiahnete.
- Závažnosti sa vykresľujú odlišnými farbami (chyba/upozornenie/informácia/rada); editor zároveň rozširuje veľmi krátke rozsahy, aby ostala jednoznaková značka viditeľná.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
Hostiteľ vždy poskytuje platný cieľ (prázdnu implementáciu, keď editor nemá plochu pre
diagnostiku), takže diagnostics() nikdy nemusíte kontrolovať na null.
Informácie pri najazdení — asynchrónny pull¶
Informácie pri najazdení riadi editor (zotrvanie myši nad symbolom). Zásuvný modul
registruje poskytovateľa, ktorý odpovedá asynchrónne; k registru sa dostanete cez
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;
}
registerProvidervraciaHoverToken(RAII); zničte ho vshutdown(), čím registráciu zrušíte.- Každý poskytovateľ sa registruje s prioritou. Zvoľte pásmo podľa toho,
čo odpovedáte, nie podľa toho, aké dôležité sa to zdá:
kFallbackpre informácie na úrovni slova alebo textu (definícia zo slovníka, cieľ URL),kLanguagepre jazykové informácie o symbole (hover jazykového servera, pravidlo lintera),kActiveSessionpre informácie, ktoré musia vyhrať, kým sú aktívne (hodnota pod ukazovateľom v ladiacom programe). Hodnoty medzi pásmami (kLanguage + 10) sú povolené, keď sa musíte zaradiť voči známemu susedovi. - Pri zotrvaní sa hostiteľ spýta všetkých poskytovateľov naraz a zobrazí neprázdnu odpoveď poskytovateľa s najvyššou prioritou; pri rovnakej priorite vyhráva poskytovateľ zaregistrovaný skôr. Odpoveď s nižšou prioritou, ktorá príde prvá, sa podrží, kým každý vyššie postavený poskytovateľ neodpovie prázdne. Keď všetci odpovedia prázdne, nezobrazí sa nič.
- Pomalý poskytovateľ nemôže zdržiavať bublinový popis donekonečna:
300 ms po požiadavke sa zobrazí najlepšia dovtedy prijatá odpoveď a
neskoršie odpovede na túto požiadavku sa zahodia. Poskytovateľ
kActiveSessionpreto musí odpovedať prázdne okamžite, keď nemá čo povedať (žiadna relácia) — inak zdrží každý hover v editore až o túto lehotu. - Odpovede, ktoré prídu po tom, čo používateľ pokračoval ďalej, sa zahodia.
- Odpoveďou je obyčajný text so značkami a vykresľuje sa vo vyskakovacom okne ako Markdown, takže nadpisy, tučné písmo aj bloky kódu fungujú — kontrakt držte v podobe reťazec dnu, reťazec von.
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);
Pomoc so signatúrou — asynchrónny pull¶
Ako pri najazdení, ale riadené napísaním ( alebo , vo volaní. Poskytovateľ odpovie aktívnou
signatúrou; hostiteľ ju vykreslí ako bublinu volania so zvýrazneným aktívnym parametrom.
K registru sa dostanete cez 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; };
Hostiteľ zobrazí prvú neprázdnu odpoveď; [activeStart, activeEnd) je bajtový rozsah
aktívneho parametra v rámci signature, použitý na zvýraznenie.
Symboly — asynchrónny pull (prechod na symbol)¶
Poskytovateľ vypisuje symboly dokumentu (a prehľadáva pracovný priestor); hostiteľ vykresľuje
výber (režimy Ctrl+Shift+O a Ctrl+T v Palete príkazov) a zhromaždené symboly si môže
vyžiadať ktorýkoľvek zásuvný modul — robí to panel Zoznam funkcií, čím je tento kontrakt
skutočne určený viacerým konzumentom. Dostanete sa k nemu cez 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;
};
- Umiestnenia sa vyjadrujú tak, ako hostiteľ naviguje: cestou
pathv súborovom systéme (prázdna znamená dokument z požiadavky) pluslinepočítaným od nuly a bajtovýmcolumnv ňom — na mieste volania nie je potrebný žiadny ďalší prevod. - Hostiteľ doručí prvý neprázdny zoznam symbolov, prípadne prázdny zoznam, keď každý poskytovateľ odpovie prázdno (takže sa čakajúci výber vždy niečo dozvie).
- Strana konzumenta:
requestDocumentSymbolsarequestWorkspaceSymbolsna registri sú náprotivkomregisterProvidertypu pull — zásuvný modul ich volá, aby si prečítal, čím prispievajú iné moduly (napríklad jazykový server). Odpoveď sa vyvolá raz, vo vlákne rozhrania, prípadne aj neskôr (asynchrónne). Panel Zoznam funkcií to využíva spôsobom opýtaj sa a maj zálohu: okamžite zobrazí vlastnú osnovu podľa regulárnych výrazov a keď príde neprázdna odpoveď, povýši sa na ňu; prázdna odpoveď základ nikdy nezmaže. Oneskorenú odpoveď zabezpečte proti rušeniu a proti novšej požiadavke (id dokumentu a generácia).
Jazyky — výpis, prepnutie, príspevok¶
Farebné zvýrazňovanie syntaxe je funkciou jadra: hostiteľ vlastní katalóg zvýrazňovačov
(dodávané súbory colorizers/*.json plus vlastné súbory používateľa), rozpozná jazyk dokumentu
z jeho cesty a prvého riadka a zobrazenie zafarbí bez akéhokoľvek zásuvného modulu.
ctx.languages() (ILanguageRegistry) je spôsob, ako sa modul pridá:
// 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)
Jazyk pridaný príspevkom sa správa presne ako dodávaný: hostiteľ vyhodnotí názvy oproti
metaúdajom lexera backendu a farby dodá aktívny motív. Zaregistrovaním id, ktoré už existuje, sa
pôvodné id nahradí na tak dlho, ako žije token; keď token zanikne, vráti sa vytlačená
definícia a otvorené dokumenty používajúce toto id sa prefarbia — takže zásuvný modul môže
dodávaný jazyk prekryť bez toho, aby katalóg natrvalo poškodil. Príspevky prežijú aj opätovné
načítanie katalógu, ktoré vyvolá Predvoľby ▸ Použiť alebo zmena motívu. Hostiteľ nemôže za behu
získať nové lexery backendu — lexer musí pomenovať taký, ktorý existuje. Platné vždy:
hostiteľ bez vrstvy zvýrazňovania poskytne prázdnu implementáciu, ktorá nevypíše nič a odmietne
každú zmenu.
Stav¶
Všetky štyri kontrakty (diagnostika, informácie pri najazdení, pomoc so signatúrou, symboly)
pribudli pred vydaním bez zvýšenia MTE_PLUGIN_API_VERSION; rovnako nasledovala jazyková plocha
(ILanguageRegistry). Ich prvým konzumentom je zásuvný modul LSP;
kontrakt pre symboly je zámerne určený viacerým konzumentom (dnes Palete príkazov, ďalej panelu
Zoznam funkcií).