Språkintelligens (diagnostikk, opplysninger ved peking, signaturhjelp, symboler)¶
En liten, backend-nøytral familie av kontrakter for språkintelligens — de dataene en
leverandør mater inn i redigererens flater for kodeintelligens. Den følger prosjektets
regel om at programtillegg bidrar med data, og kjernen tegner: bølgelinjene, boblen
ved peking og markørhoppene hører hjemme i redigererens visning og blottlegges aldri for
programtillegg; et programtillegg leverer bare POD- og std::string-data, så den samme
kontrakten senere kan oppfylles av en Python-backend.
Den første forbrukeren er LSP-programtillegget, men kanalene er allmenne — en statisk analysator eller en stavekontroll kan publisere diagnostikk på samme måte.
Hva som ikke er her
Gå til definisjonen, søk etter referanser, gi nytt navn og formater krever ingen
API for seg selv — de bare navigerer eller redigerer tekst, noe IEditorService alt
uttrykker (caret, openFile, setCaret, setText), så de blir værende kommandoer
inne i programtillegget. Fullføring gjenbruker den eksisterende
ICompletionRegistry. En ny kontrakt legges bare til når det finnes en flate i
kjernen som tegner eller velger, og kanskje flere leverandører — og det er
tilfellet for diagnostikk, opplysninger ved peking, signaturhjelp og symboler.
Diagnostikk — skyv¶
En leverandør skyver diagnostikk når den har noe (den avgjør selv når resultatene er
klare); redigereren tegner den som bølgede understrekinger. Mottakeren nås gjennom
IPluginContext::diagnostics(). Verditypene er alminnelig 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;
};
sourceer en erstatningsnøkkel per(dokument, kilde). Å publisere for en kilde erstatter den kildens tidligere diagnostikk på dokumentet, så flere produsenter (LSP, stavekontroll, byggelogg) kan leve side om side uten å slette hverandres.- Publiser en tom tabell for en kilde for å trekke diagnostikken dens tilbake.
- Alvorsgradene tegnes med hver sin farge (feil, advarsel, informasjon, tips); redigereren gjør også svært korte områder bredere, så en markør på ett enkelt tegn blir værende synlig.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
Verten stiller alltid en gyldig mottaker til rådighet (en tom utgave når redigereren ikke
har noen flate for diagnostikk), så du aldri trenger å kontrollere om diagnostics() er
null.
Opplysninger ved peking — asynkront trekk¶
Opplysninger ved peking drives av redigereren (musen hviler over et symbol). Et
programtillegg registrerer en leverandør som svarer asynkront; registeret nås
gjennom 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;
}
registerProvidergir etHoverTokentilbake (RAII); ødelegg det ishutdown()for å avregistrere.- Hver leverandør registrerer seg med en prioritet. Velg båndet som
beskriver hva du svarer, ikke hvor viktig det føles:
kFallbackfor informasjon på ord- eller tekstnivå (en ordboksdefinisjon, målet for en URL),kLanguagefor språkinformasjon om symbolet (hoveren til en språkserver, regelen til en linter),kActiveSessionfor informasjon som må vinne så lenge den er aktiv (verdien under pekeren i en feilsøker). Verdier mellom båndene (kLanguage + 10) er tillatt når du må ordne deg i forhold til en kjent nabo. - Når musen hviler, spør verten alle leverandørene samtidig og viser det ikke-tomme svaret fra leverandøren med høyest prioritet; ved lik prioritet vinner leverandøren som ble registrert først. Et svar med lavere prioritet som kommer først, holdes tilbake til hver høyere leverandør har svart tomt. Når alle svarer tomt, vises ingenting.
- En treg leverandør kan ikke holde verktøytipset tilbake for alltid:
300 ms etter forespørselen vises det beste svaret som er mottatt så
langt, og senere svar på den forespørselen kastes. En
kActiveSession-leverandør må derfor svare tomt med en gang når den ikke har noe å si (ingen økt) — ellers forsinker den hver hover i redigeringsprogrammet med inntil tidsgrensen. - Svar som kommer etter at brukeren har gått videre, kastes.
- Svaret er alminnelig oppmerkingstekst og gjengis som Markdown i en boble, så overskrifter, halvfet skrift og kodeblokker virker — hold kontrakten til streng inn, streng ut.
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);
Signaturhjelp — asynkront trekk¶
Som opplysninger ved peking, men drevet av at man skriver et kalls ( eller et ,. En
leverandør svarer med den aktive signaturen; verten tegner den som et kalltips med den
aktive parameteren uthevet. Registeret nås gjennom 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; };
Verten viser det første ikke-tomme svaret; [activeStart, activeEnd) er den aktive
parameterens byteområde inne i signature, og brukes til uthevingen.
Symboler — asynkront trekk (gå til symbol)¶
En leverandør lister et dokuments symboler (og søker i arbeidsområdet); verten tegner
velgeren (kommandopalettens modi Ctrl+Shift+O / Ctrl+T), og ethvert programtillegg kan
trekke de samlede symbolene — det gjør Funksjonsliste-panelet, noe som gjør dette til
en ekte kontrakt med flere forbrukere. Den nås gjennom 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;
};
- Plasseringer uttrykkes slik verten navigerer: en
pathi filsystemet (tom betyr forespørselens dokument) pluss en 0-basertlineog encolumni byte inne i den — det trengs ingen videre omregning på kallstedet. - Verten leverer den første ikke-tomme symbollisten, eller en tom liste når hver leverandør har svart tomt (så en ventende velger alltid får vite noe).
- Forbrukersiden:
requestDocumentSymbols/requestWorkspaceSymbolspå registeret er trekkmotstykket tilregisterProvider— et programtillegg kaller dem for å lese det andre programtillegg (for eksempel en språkserver) bidrar med. Svaret utløses én gang, på grensesnittråden, kanskje senere (asynkront). Funksjonsliste-panelet bruker dette som spør og fall tilbake: det viser sin egen regex-oversikt med det samme og oppgraderer til svaret hvis det kommer et ikke-tomt; et tomt svar tømmer aldri utgangspunktet. Vern et sent svar mot nedriving og mot en nyere forespørsel (dokument-id pluss generasjon).
Språk — list, bytt, bidra¶
Syntaksfarging er en kjernefunksjon: verten eier en katalog av fargeleggere (de
medfølgende colorizers/*.json-filene pluss brukerens egne), finner ut et dokuments språk
ut fra stien og første linje, og farger visningen uten noe programtillegg.
ctx.languages() (ILanguageRegistry) er måten et programtillegg er med på:
// 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)
Et språk som er bidratt med, oppfører seg nøyaktig som et medfølgende: verten slår navnene
opp i backend-leksikografens metadata, og det aktive temaet leverer fargene. Å registrere
en id som alt finnes, erstatter den så lenge symbolet lever; når symbolet dør, kommer
den fortrengte definisjonen tilbake, og åpne dokumenter som bruker id-en, farges på nytt —
så et programtillegg kan overstyre et medfølgende språk uten å skade katalogen varig.
Bidrag overlever også den innlastingen av katalogen som et Innstillinger ▸ Bruk eller et
temabytte utløser. Verten kan ikke få nye backend-leksikografer under kjøringen —
lexer må navngi en som finnes. Alltid gyldig: en vert uten et fargeleggingslag leverer
en tom utgave som lister ingenting og nekter enhver endring.
Status¶
Alle fire kontraktene (diagnostikk, opplysninger ved peking, signaturhjelp, symboler) ble
lagt til før utgivelsen, uten at MTE_PLUGIN_API_VERSION ble hevet; språkflaten
(ILanguageRegistry) fulgte på samme måte. Den første forbrukeren deres er
LSP-programtillegget; symbolkontrakten har med vilje flere
forbrukere (kommandopaletten i dag, Funksjonsliste-panelet som det neste).