Inteligence jazyka (diagnostika, nápovědy při najetí, nápověda k signaturám, symboly)¶
Malá rodina kontraktů neutrálních vůči backendu pro inteligenci jazyka — data, kterými
poskytovatel krmí plochy inteligence kódu v editoru. Řídí se pravidlem projektu »zásuvné moduly
přispívají daty, jádro vykresluje«: vlnovky, vyskakovací okno s nápovědou i skoky kurzoru bydlí
v zobrazení editoru a modulům se nikdy nezpřístupňují; modul dodává pouze data typu POD a
std::string, takže tentýž kontrakt může později naplnit backend v Pythonu.
Prvním konzumentem je zásuvný modul LSP, ale kanály jsou obecné — linter nebo kontrola pravopisu může diagnostiku publikovat stejně.
Co zde není
Přechod na definici, hledání odkazů, přejmenování a formátování nepotřebují žádné vlastní
rozhraní — jen navigují v textu nebo jej upravují, což IEditorService už vyjadřuje
(caret, openFile, setCaret, setText), takže zůstávají příkazy uvnitř modulu.
Dokončování využívá stávající ICompletionRegistry. Nový kontrakt se přidává jen tehdy,
existuje-li v jádře vykreslovací nebo výběrová plocha a potenciálně několik
poskytovatelů — což je případ diagnostiky, nápovědy při najetí, nápovědy k signaturám a
symbolů.
Diagnostika — vysílání¶
Poskytovatel diagnostiku vysílá, kdykoli ji má (sám rozhoduje, kdy jsou výsledky připravené);
editor ji vykreslí jako vlnovkovitá podtržení. K příjemci se dostanete přes
IPluginContext::diagnostics(). Hodnotové typy jsou obyčejné 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 klíč nahrazení pro dvojici(doc, source). Publikování pro daný zdroj nahradí jeho předchozí diagnostiku na dokumentu, takže několik producentů (LSP, kontrola pravopisu, protokol sestavení) koexistuje, aniž by se navzájem přepisovali.- Publikováním prázdného vektoru pro daný zdroj jeho diagnostiku stáhnete.
- Závažnosti se vykreslují odlišnými barvami (chyba/varování/informace/nápověda); editor navíc velmi krátké rozsahy rozšíří, aby značka na jediný znak zůstala viditelná.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
Hostitel vždy poskytuje platného příjemce (prázdnou implementaci, když editor žádnou plochu pro
diagnostiku nemá), takže diagnostics() nikdy nemusíte kontrolovat na null.
Nápověda při najetí — asynchronní vyžádání¶
Nápovědu při najetí řídí editor (zastavení myši nad symbolem). Modul registruje
poskytovatele, který odpovídá asynchronně; k registru se dostanete přes
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;
}
registerProvidervracíHoverToken(RAII); zničením veshutdown()registraci zrušíte.- Každý poskytovatel se registruje s prioritou. Zvolte pásmo podle toho,
co odpovídáte, ne podle toho, jak důležité se to zdá:
kFallbackpro informace na úrovni slova nebo textu (definice ze slovníku, cíl URL),kLanguagepro jazykové informace o symbolu (hover jazykového serveru, pravidlo linteru),kActiveSessionpro informace, které musí vyhrát, dokud jsou aktivní (hodnota pod ukazatelem v ladicím programu). Hodnoty mezi pásmy (kLanguage + 10) jsou povolené, když se musíte zařadit vůči známému sousedovi. - Při zastavení myši se hostitel zeptá všech poskytovatelů najednou a zobrazí neprázdnou odpověď poskytovatele s nejvyšší prioritou; při shodné prioritě vyhrává poskytovatel zaregistrovaný dřív. Odpověď s nižší prioritou, která dorazí první, se podrží, dokud každý výše postavený poskytovatel neodpoví prázdně. Když všichni odpoví prázdně, nezobrazí se nic.
- Pomalý poskytovatel nemůže zdržovat bublinovou nápovědu donekonečna:
300 ms po požadavku se zobrazí nejlepší dosud přijatá odpověď a pozdější
odpovědi na tento požadavek se zahodí. Poskytovatel
kActiveSessionproto musí odpovědět prázdně okamžitě, když nemá co říct (žádná relace) — jinak zdrží každý hover v editoru až o tuto lhůtu. - Odpovědi, které dorazí poté, co uživatel pokračoval dál, se zahodí.
- Odpověď je prostý text se značkováním a vykresluje se ve vyskakovacím okně jako Markdown, takže nadpisy, tučné písmo i bloky kódu fungují — kontrakt držte ve tvaru řetězec dovnitř / řetězec ven.
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);
Nápověda k signaturám — asynchronní vyžádání¶
Jako nápověda při najetí, ale řízená napsáním ( nebo , ve volání. Poskytovatel odpovídá
aktivní signaturou; hostitel ji vykreslí jako tip k volání se zvýrazněným aktivním
parametrem. K registru se dostanete přes 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; };
Hostitel zobrazí první neprázdnou odpověď; [activeStart, activeEnd) je bajtový úsek
aktivního parametru uvnitř signature, použitý pro zvýraznění.
Symboly — asynchronní vyžádání (přechod na symbol)¶
Poskytovatel vyjmenovává symboly dokumentu (a prohledává pracovní prostor); hostitel vykresluje
výběr (režimy Ctrl+Shift+O / Ctrl+T v Paletě příkazů) a kterýkoli modul si může souhrnné
symboly vyžádat — což dok Seznam funkcí dělá, čímž je z toho skutečně vícekonzumentský
kontrakt. Dostanete se k němu přes 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;
};
- Umístění se vyjadřují tak, jak hostitel naviguje: cesta v souborovém systému
path(prázdná znamená dokument požadavku) pluslinepočítaný od 0 a bajtovýcolumnv něm — v místě volání není potřeba žádný další převod. - Hostitel doručí první neprázdný seznam symbolů, nebo prázdný seznam, jakmile každý poskytovatel odpoví prázdně (takže čekajícímu výběru se vždy něco řekne).
- Strana konzumenta:
requestDocumentSymbols/requestWorkspaceSymbolsna registru jsou protějškemregisterProviderpro vyžádání — modul je volá, aby si přečetl, čím přispívají jiné moduly (například jazykový server). Odpověď se spustí jednou, v uživatelském vlákně, klidně později (asynchronně). Dok Seznam funkcí to používá ve stylu zeptej se a měj záložní řešení: okamžitě zobrazí svůj vlastní přehled z regulárních výrazů a dorazí-li neprázdná odpověď, povýší na ni; prázdná odpověď základ nikdy nesmaže. Pozdní odpověď pojistěte proti strhnutí a proti novějšímu požadavku (id dokumentu + generace).
Jazyky — vyjmenování, přepnutí, přispění¶
Obarvování syntaxe je funkcí jádra: hostitel vlastní katalog obarvovačů (dodávané soubory
colorizers/*.json plus vlastní soubory uživatele), rozpozná jazyk dokumentu z jeho cesty a
prvního řádku a zobrazení obarví bez jakéhokoli modulu. ctx.languages() (ILanguageRegistry)
je způsob, jak se modul přidá:
// 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, kterým modul přispěl, se chová přesně jako dodávaný: hostitel názvy vyhodnotí proti
metadatům lexeru backendu a barvy dodá aktivní motiv. Registrace id, které už existuje, jej
nahradí na tak dlouho, dokud token žije; když token zanikne, vytlačená definice se vrátí a
otevřené dokumenty používající ono id se přebarví — takže modul může dodávaný jazyk přebít, aniž
by katalog trvale poškodil. Příspěvky přežijí i opětovné načtení katalogu, které vyvolá Předvolby
▸ Použít nebo přepnutí motivu. Hostitel nemůže za běhu získat nové lexery backendu — lexer
musí pojmenovat některý z existujících. Vždy platné: hostitel bez obarvovací vrstvy dodá prázdnou
implementaci, která nevyjmenuje nic a odmítne každou změnu.
Stav¶
Všechny čtyři kontrakty (diagnostika, nápověda při najetí, nápověda k signaturám, symboly) byly
přidány před vydáním bez zvýšení MTE_PLUGIN_API_VERSION; jazyková plocha (ILanguageRegistry)
následovala stejným způsobem. Jejich prvním konzumentem je
zásuvný modul LSP; kontrakt symbolů je záměrně vícekonzumentský (dnes
Paleta příkazů, dál dok Seznam funkcí).