Intelligenza del linguaggio (diagnostica, suggerimenti al passaggio del mouse, aiuto sulla firma, simboli)¶
Una piccola famiglia di contratti neutri rispetto al backend per
l'intelligenza del linguaggio: i dati che un fornitore dà in pasto alle
superfici di intelligenza del codice dell'editor. Segue la regola del progetto
«i plugin contribuiscono dati, il nucleo disegna»: le sottolineature ondulate,
la finestra a comparsa al passaggio del mouse e i salti del cursore vivono nella
vista dell'editor e non sono mai esposti ai plugin; un plugin fornisce solo dati
POD / std::string, così che lo stesso contratto possa in seguito essere
soddisfatto da un backend Python.
Il primo consumatore è il plugin LSP, ma i canali sono generici: un analizzatore statico o un correttore ortografico può pubblicare diagnostiche allo stesso modo.
Che cosa non c'è qui
Vai alla definizione, trova riferimenti, rinomina e formattazione non
richiedono un'API propria: si limitano a navigare o a modificare il testo,
cosa che IEditorService già esprime (caret, openFile, setCaret,
setText), quindi restano comandi interni al plugin. Il completamento
riusa l'ICompletionRegistry esistente. Un nuovo contratto viene aggiunto
solo quando esiste una superficie di disegno / di scelta nel nucleo e
potenzialmente più fornitori — il che vale per diagnostica, suggerimenti
al passaggio del mouse, aiuto sulla firma e simboli.
Diagnostica — a spinta¶
Un fornitore spinge le diagnostiche non appena le ha (è lui a decidere quando
i risultati sono pronti); l'editor le disegna come sottolineature ondulate.
Raggiungi il collettore tramite IPluginContext::diagnostics(). I tipi valore
sono semplici 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è una chiave di sostituzione per(doc, source). Pubblicare per una sorgente sostituisce le diagnostiche precedenti di quella sorgente sul documento, così che più produttori (LSP, controllo ortografico, log di compilazione) coesistano senza calpestarsi a vicenda.- Pubblica un vettore vuoto per una sorgente per ritirarne le diagnostiche.
- Le gravità vengono disegnate con colori distinti (errore/avviso/informazione/ suggerimento); l'editor allarga inoltre gli intervalli molto brevi, così che un marcatore di un solo carattere resti visibile.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
L'host fornisce sempre un collettore valido (un'operazione nulla quando l'editor
non ha una superficie per le diagnostiche), quindi non devi mai verificare che
diagnostics() non sia nullo.
Passaggio del mouse — a richiesta asincrona¶
Il suggerimento al passaggio del mouse è guidato dall'editor (il puntatore che
indugia su un simbolo). Un plugin registra un fornitore che risponde in modo
asincrono; raggiungi il registro tramite 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;
}
registerProviderrestituisce unHoverToken(RAII); distruggilo inshutdown()per annullare la registrazione.- Ogni fornitore si registra con una priorità. Scegli la fascia che
descrive che cosa rispondi, non quanto ti sembri importante:
kFallbackper informazioni a livello di parola o di testo (la definizione di un dizionario, la destinazione di un URL),kLanguageper informazioni di linguaggio sul simbolo (l'hover di un language server, la regola di un linter),kActiveSessionper informazioni che devono prevalere finché sono attive (il valore sotto il puntatore in un debugger). I valori tra le fasce (kLanguage + 10) sono ammessi quando devi posizionarti rispetto a un vicino noto. - Quando il puntatore indugia, l'host interroga tutti i fornitori insieme e mostra la risposta non vuota del fornitore con la priorità più alta; a parità di priorità vince il fornitore registrato per primo. Una risposta di priorità inferiore che arriva per prima viene trattenuta finché ogni fornitore di priorità superiore non ha risposto vuoto. Quando tutti rispondono vuoto, non viene mostrato nulla.
- Un fornitore lento non può trattenere il tooltip all'infinito: 300 ms
dopo la richiesta viene mostrata la migliore risposta ricevuta fino a quel
momento e le risposte successive a quella richiesta vengono scartate. Un
fornitore
kActiveSessiondeve quindi rispondere vuoto subito quando non ha nulla da dire (nessuna sessione), altrimenti ritarda ogni hover dell'editor fino al timeout. - Le risposte che arrivano dopo che l'utente si è spostato vengono scartate.
- La risposta è semplice testo di markup e viene resa come Markdown in una finestra a comparsa, quindi titoli, grassetto e blocchi di codice funzionano: mantieni il contratto stringa in ingresso / stringa in uscita.
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);
Aiuto sulla firma — a richiesta asincrona¶
Come il passaggio del mouse, ma guidato dalla digitazione della ( o di una ,
di una chiamata. Un fornitore risponde con la firma attiva; l'host la disegna come
un suggerimento di chiamata con il parametro attivo evidenziato. Raggiungi il
registro tramite 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; };
L'host mostra la prima risposta non vuota; [activeStart, activeEnd) è
l'intervallo di byte del parametro attivo dentro signature, usato per
l'evidenziazione.
Simboli — a richiesta asincrona (vai al simbolo)¶
Un fornitore enumera i simboli di un documento (e cerca nell'area di lavoro);
l'host disegna il selettore (le modalità Ctrl+Shift+O / Ctrl+T della tavolozza
dei comandi) e qualsiasi plugin può richiedere i simboli aggregati — lo fa il
pannello Elenco delle funzioni, il che rende questo un contratto davvero
multi-consumatore.
Raggiungilo tramite 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;
};
- Le posizioni sono espresse come l'host naviga: un
pathdel filesystem (vuoto significa il documento della richiesta) più unalinea base 0 e unacolumnin byte al suo interno — nessuna ulteriore conversione nel punto di chiamata. - L'host consegna il primo elenco di simboli non vuoto, oppure un elenco vuoto una volta che ogni fornitore ha risposto vuoto (così a un selettore in attesa viene sempre detto qualcosa).
- Lato consumatore:
requestDocumentSymbols/requestWorkspaceSymbolssul registro sono la controparte a richiesta diregisterProvider— un plugin le chiama per leggere ciò che altri plugin (per esempio un language server) contribuiscono. La risposta scatta una volta, sul thread dell'interfaccia, eventualmente più tardi (asincrona). Il pannello Elenco delle funzioni usa questo schema di chiedi e ripiega: mostra istantaneamente il proprio schema basato su espressioni regolari e, se arriva una risposta non vuota, passa a quella; una risposta vuota non azzera mai la base. Proteggi una risposta tardiva dallo smontaggio e da una richiesta più recente (id del documento + generazione).
Linguaggi — enumerare, cambiare, contribuire¶
La colorazione della sintassi è una funzionalità del nucleo: l'host possiede
un catalogo di colorizzatori (i file colorizers/*.json distribuiti più quelli
dell'utente), rileva il linguaggio di un documento dal suo percorso e dalla prima
riga, e colora la vista senza alcun plugin. ctx.languages()
(ILanguageRegistry) è il modo in cui un plugin partecipa:
// 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)
Un linguaggio contribuito si comporta esattamente come uno distribuito: l'host
risolve i nomi rispetto ai metadati del lexer del backend e il tema attivo
fornisce i colori. Registrare un id che esiste già lo sostituisce per tutta la
vita del token; quando il token muore la definizione spodestata torna e i
documenti aperti che usano quell'id vengono ricolorati — così un plugin può
scavalcare un linguaggio distribuito senza danneggiare in modo permanente il
catalogo. I contributi sopravvivono anche alla ricarica del catalogo innescata da
un Preferenze ▸ Applica o da un cambio di tema. L'host non può acquisire nuovi
lexer di backend a tempo di esecuzione: lexer deve nominarne uno esistente.
Sempre valido: un host senza uno strato di colorazione fornisce un'operazione
nulla che non enumera nulla e rifiuta ogni modifica.
Stato¶
Tutti e quattro i contratti (diagnostica, passaggio del mouse, aiuto sulla firma,
simboli) sono stati aggiunti prima del rilascio senza incrementare
MTE_PLUGIN_API_VERSION; la superficie dei linguaggi (ILanguageRegistry) ha
seguito la stessa strada. Il loro primo consumatore è il
plugin LSP; il contratto dei simboli è
intenzionalmente multi-consumatore (oggi la tavolozza dei comandi, poi il
pannello Elenco delle funzioni).