Przejdź do treści

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;
};
  • source to 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) = 0;
};
  • registerProvider zwraca HoverToken (RAII); zniszcz go w shutdown(), aby się wyrejestrować.
  • Przy zatrzymaniu kursora host odpytuje każdego zarejestrowanego providera i pokazuje pierwszą niepustą odpowiedź; 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);

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: path w systemie plików (puste oznacza dokument z żądania) plus liczony od 0 line i bajtowy column w 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 / requestWorkspaceSymbols na rejestrze to pull-owy odpowiednik registerProvider — 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).

Status

Wszystkie cztery kontrakty (diagnostyka, hover, signature help, symbole) zostały dodane przed wydaniem bez podbijania MTE_PLUGIN_API_VERSION. Ich pierwszym konsumentem jest wtyczka LSP; kontrakt symboli jest celowo wielokonsumencki (dziś paleta poleceń, następny w kolejce dok Function List).