Sprachintelligenz (Diagnostik, Hover, Signaturhilfe, Symbole)¶
Eine kleine, Backend-neutrale Vertragsfamilie für Sprachintelligenz — die
Daten, die ein Provider den Code-Intelligenz-Flächen des Editors zuführt. Sie
folgt der Projektregel „Plugins liefern Daten, der Kern rendert“: Die
Unterschlängelungen, das Hover-Popup und Caret-Sprünge leben in der
Editor-Ansicht und werden Plugins niemals offengelegt; ein Plugin liefert nur
POD-/std::string-Daten, sodass derselbe Vertrag später von einem
Python-Backend erfüllt werden kann.
Der erste Konsument ist das LSP-Plugin, aber die Kanäle sind generisch — ein Linter oder eine Rechtschreibprüfung kann Diagnosen auf demselben Weg veröffentlichen.
Was hier nicht enthalten ist
Go-to-Definition, Find-References, Rename und Formatierung brauchen
keine eigene API — sie navigieren oder bearbeiten nur Text, was
IEditorService bereits ausdrückt (caret, openFile, setCaret,
setText), also bleiben sie Plugin-interne Commands. Die
Vervollständigung nutzt das bestehende ICompletionRegistry weiter. Ein
neuer Vertrag wird nur hinzugefügt, wenn es eine zentrale
Render-/Auswahlfläche im Kern gibt und potenziell mehrere
Provider — was für Diagnostik, Hover, Signaturhilfe und Symbole der Fall
ist.
Diagnostik — Push¶
Ein Provider pusht Diagnosen, wann immer er welche hat (er entscheidet,
wann Ergebnisse fertig sind); der Editor rendert sie als geschlängelte
Unterstreichungen. Den Sink erreichen Sie über
IPluginContext::diagnostics(). Die Wertetypen sind reine 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;
};
sourceist ein Ersetzungsschlüssel pro(doc, source). Das Veröffentlichen für eine Quelle ersetzt die vorherigen Diagnosen dieser Quelle im Dokument, sodass mehrere Produzenten (LSP, Rechtschreibprüfung, Build-Log) koexistieren, ohne sich gegenseitig zu überschreiben.- Veröffentlichen Sie einen leeren Vektor für eine Quelle, um deren Diagnosen zurückzuziehen.
- Schweregrade werden mit unterschiedlichen Farben gerendert (Error/Warning/Info/Hint); der Editor verbreitert außerdem sehr kurze Bereiche, damit eine Ein-Zeichen-Markierung sichtbar bleibt.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
Der Host stellt immer einen gültigen Sink bereit (ein No-op, wenn der Editor
keine Diagnostikfläche hat), sodass Sie diagnostics() niemals auf null
prüfen müssen.
Hover — asynchroner Pull¶
Hover wird vom Editor gesteuert (Verweilen der Maus über einem Symbol).
Ein Plugin registriert einen Provider, der asynchron antwortet; die
Registry erreichen Sie über 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;
};
registerProviderliefert einHoverToken(RAII) zurück; zerstören Sie es inshutdown(), um die Registrierung aufzuheben.- Beim Verweilen fragt der Host jeden registrierten Provider ab und zeigt die erste nicht-leere Antwort; Antworten, die eintreffen, nachdem der Benutzer weitergezogen ist, werden verworfen.
- Die Antwort ist einfacher Markup-Text und wird als Markdown in einem Popup gerendert, sodass Überschriften, Fettdruck und Codeblöcke funktionieren — halten Sie den Vertrag String-rein / String-raus.
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);
Signaturhilfe — asynchroner Pull¶
Wie Hover, aber ausgelöst durch das Tippen des ( eines Aufrufs oder eines
,. Ein Provider beantwortet die aktive Signatur; der Host rendert sie als
Call-Tip mit hervorgehobenem aktiven Parameter. Die Registry erreichen
Sie über 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; };
Der Host zeigt die erste nicht-leere Antwort; [activeStart, activeEnd)
ist der Byte-Bereich des aktiven Parameters innerhalb von signature,
der für die Hervorhebung verwendet wird.
Symbole — asynchroner Pull (Go-to-Symbol)¶
Ein Provider zählt die Symbole eines Dokuments auf (und durchsucht den
Workspace); der Host rendert den Auswahldialog (die Modi Ctrl+Shift+O /
Ctrl+T der Command Palette), und jedes Plugin kann die aggregierten
Symbole pullen — das Function-List-Dock tut es, was diesen Vertrag zu
einem echten Multi-Konsumenten-Vertrag macht.
Sie erreichen ihn über 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;
};
- Orte werden so ausgedrückt, wie der Host navigiert: ein Dateisystem-
path(leer bedeutet das Dokument der Anfrage) plus eine 0-basiertelineund eine Byte-columndarin — am Aufrufort ist keine weitere Umrechnung nötig. - Der Host liefert die erste nicht-leere Symbolliste, oder eine leere Liste, sobald jeder Provider leer geantwortet hat (ein wartender Auswahldialog bekommt also immer eine Antwort).
- Konsumentenseite:
requestDocumentSymbols/requestWorkspaceSymbolsauf der Registry sind das Pull-Gegenstück zuregisterProvider— ein Plugin ruft sie auf, um zu lesen, was andere Plugins (z. B. ein Language-Server) beisteuern. Die Antwort feuert einmal, auf dem UI-Thread, möglicherweise später (asynchron). Das Function-List-Dock nutzt dieses Fragen-und-Zurückfallen: Es zeigt sofort seine eigene Regex-Gliederung und wertet, falls eine nicht-leere Antwort eintrifft, auf diese auf; eine leere Antwort löscht die Basislinie niemals. Sichern Sie eine späte Antwort gegen den Abbau und gegen eine neuere Anfrage ab (Dokument-Id + Generation).
Status¶
Alle vier Verträge (Diagnostik, Hover, Signaturhilfe, Symbole) wurden vor der
Veröffentlichung hinzugefügt, ohne MTE_PLUGIN_API_VERSION zu erhöhen. Ihr
erster Konsument ist das LSP-Plugin; der
Symbolvertrag ist bewusst multi-konsumentenfähig (heute die Command Palette,
als Nächstes das Function-List-Dock).