Kieliäly (diagnostiikka, osoitusvihjeet, allekirjoitusohje, symbolit)¶
Pieni, taustajärjestelmäneutraali sopimusperhe kieliälylle — sille datalle, jota
toimittaja syöttää editorin koodiälypinnoille. Se noudattaa projektin sääntöä
»liitännäiset tarjoavat dataa, ydin piirtää«: aaltoviivat, osoitusponnahdusikkuna ja
kohdistinhypyt sijaitsevat editorinäkymässä eikä niitä koskaan paljasteta liitännäisille;
liitännäinen toimittaa vain POD- ja std::string-dataa, jotta sama sopimus voidaan
myöhemmin toteuttaa Python-taustajärjestelmällä.
Ensimmäinen kuluttaja on LSP-liitännäinen, mutta kanavat ovat yleisiä — staattinen analysaattori tai oikolukija voi julkaista diagnostiikkaa samalla tavalla.
Mitä täällä ei ole
Siirry määrittelyyn, etsi viittaukset, nimeä uudelleen ja muotoilu eivät tarvitse
omaa API:ta — ne vain navigoivat tai muokkaavat tekstiä, jonka IEditorService jo
ilmaisee (caret, openFile, setCaret, setText), joten ne pysyvät liitännäisen
sisäisinä komentoina. Täydennys käyttää olemassa olevaa rajapintaa
ICompletionRegistry. Uusi sopimus lisätään vain, kun ytimessä on piirto- tai
valintapinta ja mahdollisesti useita toimittajia — mikä pätee diagnostiikkaan,
osoitusvihjeisiin, allekirjoitusohjeeseen ja symboleihin.
Diagnostiikka — työntö¶
Toimittaja työntää diagnostiikkaa, kun sillä on sitä (se päättää, milloin tulokset ovat
valmiit); editori piirtää ne aaltoviiva-alleviivauksina. Vastaanottaja tavoitetaan
funktiolla IPluginContext::diagnostics(). Arvotyypit ovat tavallista POD-dataa
(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;
};
sourceon korvausavain paria(asiakirja, lähde)kohti. Julkaiseminen lähdettä varten korvaa tuon lähteen aiemman diagnostiikan asiakirjassa, joten useat tuottajat (LSP, oikoluku, käännösloki) elävät rinnakkain sotkematta toistensa tuloksia.- Julkaise tyhjä taulukko lähteelle vetääksesi sen diagnostiikan takaisin.
- Vakavuusasteet piirretään eri väreillä (virhe, varoitus, tieto, vihje); editori myös levittää hyvin lyhyitä alueita, jotta yhden merkin merkintä pysyy näkyvänä.
ctx.diagnostics().publishDiagnostics(doc, "org.mycompany.linter", {
{ byteRange, DiagnosticSeverity::Warning, "unused variable", "W001" },
});
Isäntä tarjoaa aina kelvollisen vastaanottajan (tyhjän toteutuksen, kun editorilla ei ole
diagnostiikkapintaa), joten sinun ei koskaan tarvitse tarkistaa, onko diagnostics() null.
Osoitusvihjeet — asynkroninen veto¶
Osoitusvihjeitä ohjaa editori (hiiri viipyy symbolin päällä). Liitännäinen
rekisteröi toimittajan, joka vastaa asynkronisesti; rekisteri tavoitetaan funktiolla
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;
}
registerProviderpalauttaa olionHoverToken(RAII); tuhoa se funktiossashutdown()poistaaksesi rekisteröinnin.- Jokainen toimittaja rekisteröityy prioriteetilla. Valitse kaista sen
mukaan, mitä vastaat, älä sen mukaan, miltä tärkeys tuntuu:
kFallbacksana- tai tekstitason tiedolle (sanakirjan määritelmä, URL:n kohde),kLanguagekielitason tiedolle symbolista (kielipalvelimen hover, linterin sääntö),kActiveSessiontiedolle, jonka täytyy voittaa niin kauan kuin se on aktiivinen (osoittimen alla oleva arvo debuggerissa). Kaistojen väliset arvot (kLanguage + 10) ovat sallittuja, kun sinun on järjestettävä itsesi tunnetun naapurin suhteen. - Viipyessä isäntä kysyy kaikilta toimittajilta kerralla ja näyttää korkeimman prioriteetin toimittajan ei-tyhjän vastauksen; saman prioriteetin kesken voittaa ensin rekisteröity toimittaja. Ensimmäisenä saapuva matalamman prioriteetin vastaus pidätetään, kunnes jokainen korkeampi toimittaja on vastannut tyhjää. Kun kaikki vastaavat tyhjää, mitään ei näytetä.
- Hidas toimittaja ei voi pidättää työkaluvihjettä ikuisesti: 300 ms
pyynnön jälkeen näytetään paras siihen mennessä saatu vastaus, ja myöhemmät
vastaukset tuohon pyyntöön hylätään.
kActiveSession-toimittajan on siksi vastattava tyhjää heti, kun sillä ei ole sanottavaa (ei istuntoa) — muuten se viivästyttää jokaista editorin hoveria enintään aikakatkaisun verran. - Vastaukset, jotka saapuvat käyttäjän jo siirryttyä eteenpäin, hylätään.
- Vastaus on tavallista merkkaustekstiä ja se piirretään Markdownina ponnahdusikkunassa, joten otsikot, lihavointi ja koodilohkot toimivat — pidä sopimus muodossa merkkijono sisään, merkkijono ulos.
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);
Allekirjoitusohje — asynkroninen veto¶
Kuten osoitusvihjeet, mutta sen ohjaa kutsun (-merkin tai ,-merkin kirjoittaminen.
Toimittaja vastaa aktiivisella allekirjoituksella; isäntä piirtää sen kutsuvihjeenä
aktiivinen parametri korostettuna. Rekisteri tavoitetaan funktiolla
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; };
Isäntä näyttää ensimmäisen ei-tyhjän vastauksen; [activeStart, activeEnd) on
aktiivisen parametrin tavuväli merkkijonon signature sisällä, jota käytetään
korostukseen.
Symbolit — asynkroninen veto (siirry symboliin)¶
Toimittaja luettelee asiakirjan symbolit (ja hakee työtilasta); isäntä piirtää valitsimen
(komentopaletin tilat Ctrl+Shift+O / Ctrl+T), ja mikä tahansa liitännäinen voi vetää
koostetut symbolit — niin tekee Funktioluettelo-telakka, mikä tekee tästä aidosti
monikuluttajaisen sopimuksen. Se tavoitetaan funktiolla 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;
};
- Sijainnit ilmaistaan niin kuin isäntä navigoi: tiedostojärjestelmän
path(tyhjä tarkoittaa pyynnön asiakirjaa) sekä nollasta alkavalineja tavuperusteinencolumnsen sisällä — kutsupaikassa ei tarvita lisämuunnosta. - Isäntä toimittaa ensimmäisen ei-tyhjän symboliluettelon tai tyhjän luettelon, kun jokainen toimittaja on vastannut tyhjällä (jotta odottavalle valitsimelle kerrotaan aina jotain).
- Kuluttajapuoli: rekisterin funktiot
requestDocumentSymbols/requestWorkspaceSymbolsovat funktionregisterProvidervetovastine — liitännäinen kutsuu niitä lukeakseen sen, mitä muut liitännäiset (esimerkiksi kielipalvelin) tarjoavat. Vastaus laukeaa kerran, käyttöliittymäsäikeessä, mahdollisesti myöhemmin (asynkronisesti). Funktioluettelo-telakka käyttää tätä tapaan kysy ja turvaudu varalle: se näyttää oman säännöllisiin lausekkeisiin perustuvan yleiskuvansa heti ja päivittää sen, jos ei-tyhjä vastaus saapuu; tyhjä vastaus ei koskaan tyhjennä perustasoa. Suojaa myöhäinen vastaus purkamiselta ja uudemmalta pyynnöltä (asiakirjan tunniste ja sukupolvi).
Kielet — luettele, vaihda, tarjoa¶
Syntaksivärjäys on ytimen ominaisuus: isäntä omistaa värjääjäluettelon (mukana tulevat
colorizers/*.json-tiedostot sekä käyttäjän omat), tunnistaa asiakirjan kielen sen polusta
ja ensimmäisestä rivistä ja värittää näkymän ilman liitännäistä. ctx.languages()
(ILanguageRegistry) on se tapa, jolla liitännäinen osallistuu:
// 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)
Tarjottu kieli käyttäytyy täsmälleen kuten mukana tuleva: isäntä ratkaisee nimet
taustajärjestelmän leksikaalisen jäsentimen metatietoja vasten ja aktiivinen teema toimittaa
värit. Jo olemassa olevan tunnisteen rekisteröinti korvaa sen niin kauan kuin poletti
elää; kun poletti kuolee, syrjäytetty määritys palaa ja avoimet asiakirjat, jotka käyttävät
tunnistetta, väritetään uudelleen — joten liitännäinen voi ohittaa mukana tulevan kielen
vahingoittamatta luetteloa pysyvästi. Tarjoukset selviävät myös siitä luettelon
uudelleenlatauksesta, jonka Asetukset ▸ Ota käyttöön tai teeman vaihto laukaisee. Isäntä ei
voi saada uusia taustajärjestelmän leksikaalisia jäsentimiä ajon aikana — lexer-kentän
täytyy nimetä sellainen, joka on olemassa. Aina kelvollinen: isäntä ilman värjäyskerrosta
toimittaa tyhjän toteutuksen, joka ei luettele mitään ja hylkää jokaisen muutoksen.
Tila¶
Kaikki neljä sopimusta (diagnostiikka, osoitusvihjeet, allekirjoitusohje, symbolit)
lisättiin ennen julkaisua nostamatta arvoa MTE_PLUGIN_API_VERSION; kielipinta
(ILanguageRegistry) seurasi samalla tavalla. Niiden ensimmäinen kuluttaja on
LSP-liitännäinen; symbolisopimus on tarkoituksella
monikuluttajainen (komentopaletti tänään, Funktioluettelo-telakka seuraavaksi).