Inteligencja językowa (diagnostyka, hover, signature help, symbole)¶
Mała, niezależna od backendu rodzina kontraktów dla inteligencji językowej —
danych, które provider dostarcza do powierzchni code intelligence edytora.
Realizuje projektową zasadę „wtyczki dostarczają dane, rdzeń renderuje":
podkreślenia, popup hovera i skoki karetki żyją w widoku edytora i nigdy nie są
udostępniane wtyczkom; wtyczka dostarcza wyłącznie dane POD/std::string, więc
ten sam kontrakt może później realizować backend Pythona.
Pierwszym konsumentem jest wtyczka LSP, ale kanały są generyczne — linter czy sprawdzanie pisowni mogą publikować diagnostykę w ten sam sposób.
Czego tu nie ma
Go-to-definition, find-references, zmiana nazwy i formatowanie nie
potrzebują żadnego własnego API — tylko nawigują lub edytują tekst, co
IEditorService już wyraża (caret, openFile, setCaret, setText),
więc pozostają poleceniami wewnątrz wtyczki. Uzupełnianie wykorzystuje
istniejący ICompletionRegistry. Nowy kontrakt dodawany jest tylko wtedy,
gdy istnieje rdzenna powierzchnia renderowania/wyboru i potencjalnie
kilku providerów — a tak jest w przypadku diagnostyki, hovera,
signature help i symboli.
Diagnostyka — push¶
Provider pcha (push) diagnostykę, kiedy tylko ją ma (sam decyduje, kiedy
wyniki są gotowe); edytor renderuje ją jako faliste podkreślenia. Dostęp do
ujścia przez IPluginContext::diagnostics(). Typy wartości to zwykłe 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;
};
sourceto klucz podmiany per(doc, source). Publikacja dla danego źródła zastępuje jego poprzednią diagnostykę na dokumencie, więc wielu producentów (LSP, sprawdzanie pisowni, log builda) współistnieje bez nadpisywania się nawzajem.- Opublikuj pusty wektor dla źródła, aby wycofać jego diagnostykę.
- Poziomy ważności renderowane są odrębnymi kolorami (error/warning/info/hint); edytor poszerza też bardzo krótkie zakresy, aby znacznik jednoznakowy pozostał widoczny.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
Host zawsze dostarcza prawidłowe ujście (no-op, gdy edytor nie ma powierzchni
diagnostyki), więc nigdy nie musisz sprawdzać diagnostics() pod kątem null.
Hover — asynchroniczny pull¶
Hover jest sterowany przez edytor (zatrzymanie myszy nad symbolem). Wtyczka
rejestruje providera, który odpowiada asynchronicznie; dostęp do rejestru
przez 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;
}
registerProviderzwracaHoverToken(RAII); zniszcz go wshutdown(), aby się wyrejestrować.- Każdy provider rejestruje się z priorytetem. Wybierz pasmo, które mówi,
co odpowiadasz, a nie jak ważny się wydajesz:
kFallbackdla informacji na poziomie słowa lub tekstu (definicja ze słownika, cel adresu URL),kLanguagedla informacji językowej o symbolu (hover serwera języka, reguła lintera),kActiveSessiondla informacji, która musi wygrywać, dopóki jest aktywna (wartość pod kursorem w debuggerze). Wartości między pasmami (kLanguage + 10) są dozwolone, gdy musisz ustawić się względem znanego sąsiada. - Przy zatrzymaniu kursora host pyta wszystkich providerów naraz i pokazuje niepustą odpowiedź providera o najwyższym priorytecie; przy równych priorytetach wygrywa provider zarejestrowany wcześniej. Odpowiedź o niższym priorytecie, która przyjdzie pierwsza, czeka, aż każdy wyższy provider odpowie pusto. Gdy wszyscy odpowiedzą pusto, nic nie jest pokazywane.
- Wolny provider nie może wstrzymywać podpowiedzi w nieskończoność: 300 ms
po zapytaniu pokazywana jest najlepsza odpowiedź otrzymana do tej pory, a
późniejsze odpowiedzi na to zapytanie są odrzucane. Provider
kActiveSessionmusi więc odpowiedzieć pusto od razu, gdy nie ma nic do powiedzenia (brak sesji) — inaczej opóźnia każdy hover w edytorze nawet o cały ten czas. - Odpowiedzi przychodzące po tym, jak użytkownik przeszedł dalej, są odrzucane.
- Odpowiedź to zwykły tekst z formatowaniem, renderowany w popupie jako Markdown, więc nagłówki, pogrubienia i bloki kodu działają — kontrakt pozostaje string na wejściu / string na wyjściu.
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);
Signature help — asynchroniczny pull¶
Jak hover, ale wyzwalany wpisaniem ( wywołania lub ,. Provider odpowiada
aktywną sygnaturą; host renderuje ją jako call tip z podświetlonym aktywnym
parametrem. Dostęp do rejestru przez 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; };
Host pokazuje pierwszą niepustą odpowiedź; [activeStart, activeEnd) to
bajtowy zakres aktywnego parametru wewnątrz signature, używany do
podświetlenia.
Symbole — asynchroniczny pull (go-to-symbol)¶
Provider wylicza symbole dokumentu (i przeszukuje przestrzeń roboczą); host
renderuje picker (tryby Ctrl+Shift+O / Ctrl+T palety poleceń), a każda
wtyczka może pobrać (pull) zagregowane symbole — robi to dok Function List,
co czyni ten kontrakt autentycznie wielokonsumenckim.
Dostęp przez 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;
};
- Lokalizacje wyrażane są tak, jak nawiguje host:
pathw systemie plików (puste oznacza dokument z żądania) plus liczony od 0linei bajtowycolumnw jego obrębie — w miejscu wywołania nie trzeba już nic konwertować. - Host dostarcza pierwszą niepustą listę symboli, albo pustą listę, gdy wszyscy providerzy odpowiedzieli pusto (więc czekający picker zawsze dostaje jakąś odpowiedź).
- Strona konsumenta:
requestDocumentSymbols/requestWorkspaceSymbolsna rejestrze to pull-owy odpowiednikregisterProvider— wtyczka wywołuje je, aby odczytać to, co dostarczają inne wtyczki (np. serwer języka). Odpowiedź przychodzi raz, w wątku UI, być może później (asynchronicznie). Dok Function List używa tego w trybie zapytaj-i-wróć-do-bazy: natychmiast pokazuje własny zarys oparty na regexach, a jeśli przyjdzie niepusta odpowiedź, przechodzi na nią; pusta odpowiedź nigdy nie czyści linii bazowej. Zabezpiecz spóźnioną odpowiedź przed sprzątaniem obiektów i przed nowszym żądaniem (id dokumentu + numer generacji).
Języki — wyliczanie, przełączanie, wnoszenie¶
Kolorowanie składni to funkcja core: host ma własny katalog colorizerów
(dostarczone pliki colorizers/*.json plus pliki użytkownika), rozpoznaje
język dokumentu po ścieżce i pierwszej linii i koloruje widok bez żadnej
wtyczki. ctx.languages() (ILanguageRegistry) pozwala wtyczce się
dołączyć:
// 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)
Wniesiony język zachowuje się dokładnie jak dostarczony: host rozwiązuje
nazwy względem metadanych leksera backendu, a aktywny motyw dostarcza kolory.
Rejestracja id, które już istnieje, zastępuje je na czas życia tokenu;
gdy token umiera, wyparta definicja wraca, a otwarte dokumenty używające tego
id są kolorowane ponownie — wtyczka może więc nadpisać dostarczony język bez
trwałego uszkodzenia katalogu. Wkłady przeżywają też przeładowanie katalogu
wywoływane przez Preferencje ▸ Zastosuj albo zmianę motywu. Host nie zyskuje
nowych lekserów backendu w czasie działania — lexer musi wskazywać
istniejący. Zawsze poprawny: host bez warstwy kolorowania dostarcza no-op,
który nic nie wylicza i odrzuca każdą zmianę.
Status¶
Wszystkie cztery kontrakty (diagnostyka, hover, signature help, symbole) zostały
dodane przed wydaniem bez podbijania MTE_PLUGIN_API_VERSION; powierzchnia
języków (ILanguageRegistry) doszła tą samą drogą. Ich pierwszym
konsumentem jest wtyczka LSP; kontrakt symboli jest
celowo wielokonsumencki (dziś paleta poleceń, następny w kolejce dok Function
List).