Programtillegg-API-et — oversikt¶
Alle PluginApi-typer ligger i namespace MTE::plugin. De offentlige hodefilene må ikke
inkludere noen Qt-, Scintilla- eller MTE-intern hodefil — bare standardbiblioteket. Det
ene bevisste unntaket er den valgfrie PluginApi/Qt/-mappen (PluginQtGlue.h,
ISettingsPage.h, ThemeFormat.h) for programtillegg som leverer sine egne
Qt-kontroller.
Den samme kontrakten oppfylles av to backender: den innebygde Qt-innlasteren (delte
biblioteker) og Python-backenden som kjører i sin egen prosess
(Python-programtillegg). Dette avsnittet beskriver
C++-flaten; hver tjeneste her som er backend-nøytral, har et motstykke på ctx i Python.
Programtilleggets livssyklus¶
Et innebygd programtillegg implementerer én klasse:
class IPlugin {
public:
virtual PluginInfo info() const = 0; // id, name, version, apiVersion
virtual bool initialize(IPluginContext& ctx) = 0; // register everything here
virtual void shutdown() noexcept = 0; // release every token
};
info()må gi sammeidogapiVersiontilbake somplugin.json; verten kryssjekker og nekter å laste inn ved uoverensstemmelse.initialize()kjører én gang ved oppstart (etter at hvert programtillegg er oppdaget). Registrer kommandoer, menyer, leverandører og paneler her; gi barefalsetilbake ved en virkelig feil (verten laster da programtillegget ut og noterer det).shutdown()kjører ved avslutning (eller når programtillegget holder på å bli lastet ut). Den må ikke kaste unntak; kallreset()på hvert symbol, og sett de lagrede tjenestepekerne dine til null.
RAII-symboler¶
Hver registrering gir et symbol tilbake (CommandToken, MenuToken, DockToken,
SettingsPageToken, Subscription …) — et BasicToken som bare kan flyttes, og som
eier registreringen: ødelegger man det eller kaller reset() på det, tilbakekalles
kommandoen, menyposten, panelet eller abonnementet. Behold hvert symbol som et medlem, og
frigi det i shutdown(). To tilganger betyr noe:
id()— en ikke-ødeleggendeTokenId-visning, for API-er som adresserer elementet uten å overta det (for eksempelIMenuRegistry::setItemChecked/setItemEnabled).release()— løsner og gir id-en tilbake; symbolet tilbakekaller ikke lenger noe. Sjelden det du vil ha.
Tjenestekatalogen¶
initialize() mottar en IPluginContext, et tjenestekatalog som verten eier. Lagre
ikke konteksten ut over initialize(); lagre de enkelte tjenestereferansene du
trenger (hver tjeneste overlever hvert programtillegg).
| Tilgang | Grensesnitt | Brukes til |
|---|---|---|
ctx.editor() |
IEditorService |
å lese og redigere bufferen, merkingen, markøren, filer, søk, bokmerker, innrykk og grupper i delt visning |
ctx.commands() |
ICommandRegistry |
å registrere og kalle brukerhandlinger (med hurtigtaster) |
ctx.menus() |
IMenuRegistry |
menyposter, skillelinjer, avkryssbare brytere og å nedtone poster |
ctx.docks() |
IDockRegistry |
dokkbare sidepaneler |
ctx.documents() |
IDocumentTypeRegistry |
fullverdige fanetyper i hovedvinduet (sammenligning, loggvisning …) |
ctx.compare() |
ICompareService |
å åpne sammenligningsvisningen med to ruter |
ctx.diagnostics() |
IDiagnosticsSink |
å publisere bølgelinjer og margtegn |
ctx.events() |
IEventBus |
å abonnere på og publisere redigererhendelser |
ctx.completions() |
ICompletionRegistry |
å bidra med kandidater til automatisk fullføring |
ctx.contextMenu() |
IContextMenuRegistry |
dynamiske poster i redigererens høyreklikkmeny |
ctx.snippetSessions() |
ISnippetSessionService |
å levere kodebiter som foldes ut med Tab (verten kjører feltøkten) |
ctx.hover() |
IHoverRegistry |
bobler med opplysninger ved peking |
ctx.signatureHelp() |
ISignatureHelpRegistry |
kalltips ved ( / , |
ctx.symbols() |
ISymbolRegistry |
symboler i dokumentet og arbeidsområdet (gå til symbol) |
ctx.languages() |
ILanguageRegistry |
å liste språk, lese og bytte et dokuments språk og bidra med en språkdefinisjon |
ctx.statusBar() |
IStatusBar |
forbigående meldinger og varige avsnitt |
ctx.settings() |
ISettings |
varig nøkkel/verdi-oppsett per programtillegg |
ctx.appSettings() |
const ISettings& |
å lese innstillinger for hele programmet (AppSettingsKeys.h) |
ctx.documentSettings() |
IDocumentSettings |
oppsett per dokument i lag med .editorconfig |
ctx.settingsRegistry() |
ISettingsRegistry |
å legge til en side i innstillingsvinduet |
ctx.themePalette() |
IThemePalette |
navngitte temafarger og integrasjon med Temaredigereren |
ctx.lineMarkers() |
ILineMarkerService |
margsymboler og linjefarging som følger redigeringer (stoppunkter, endringsstriper); kravet på margklikket |
ctx.log() |
ILogger |
trace / debug / info / warning / error |
ctx.pluginDataDir() |
std::filesystem::path |
din egen private skrivbare mappe |
ctx.diagnosticsDir() |
std::filesystem::path |
den felles mappen for krasjspor og logger (skrivebeskyttet for programtillegg) |
Å utvide API-et senere betyr å legge til et nytt tjenestegrensesnitt og en ny tilgang — et forenlig tillegg etter versjonsregelen: programtillegg som alt finnes, lastes fortsatt inn. Den verktøykjeden et programtilleggsbygg selv må passe til (kompilator, kjøretidsbibliotek, Qt-underversjon), er listet under Å skrive et programtillegg, «Byggekrav».
IEditorService — dokumentmodellen¶
Redigereren slik programtillegg ser den — ingen Scintilla, ingen Qt. Dokumenter vises det
til med en ugjennomsiktig DocumentId (std::uint64_t, 0 = ugyldig). Posisjoner er
byteposisjoner i UTF-8-tekst; MatchRange er et halvåpent {start, end} der
noMatch() betyr «ikke funnet»; EditorPosition er et 0-basert {line, column}.
// Documents
DocumentId activeDocument() const;
std::vector<DocumentId> openDocuments() const;
void setActiveDocument(DocumentId);
std::optional<std::filesystem::path> filePath(DocumentId) const;
DocumentId openFile(std::filesystem::path);
DocumentId openFileAt(std::filesystem::path, EditorPosition); // jump there
void revealLine(DocumentId, std::int64_t line); // show, don't move
bool saveDocument(DocumentId);
bool closeDocument(DocumentId); // prompts if modified
bool isModified(DocumentId) const;
DocumentEncoding documentEncoding(DocumentId) const;
void setDocumentEncoding(DocumentId, DocumentEncoding);
LineEnding lineEnding(DocumentId) const;
void setLineEnding(DocumentId, LineEnding);
// Text, selection, caret
std::string text(DocumentId) const; // whole buffer
std::string textRange(DocumentId, start, end) const;
void setText(DocumentId, std::string_view);
std::int64_t length(DocumentId) const;
std::string selectedText(DocumentId) const;
void replaceSelection(DocumentId, std::string_view);
MatchRange selectionRange(DocumentId) const;
void setSelection(DocumentId, MatchRange);
EditorPosition caret(DocumentId) const;
void setCaret(DocumentId, EditorPosition);
// Search & replace (SearchOptions: case, whole-word, regex, wrap)
MatchRange findInRange(DocumentId, needle, range, SearchOptions) const;
void replaceTarget(DocumentId, MatchRange, std::string_view);
// Bookmarks, marks, URL highlighting
void toggleBookmark(DocumentId, line); std::vector<std::int64_t> bookmarkedLines(DocumentId) const;
void markRange(DocumentId, MatchRange); void clearMarks(DocumentId);
// Language & indentation
std::string colorizerId(DocumentId) const; // "cpp", "" if none
IndentationStyle indentation(DocumentId) const; // {useTabs, width}
void setIndentation(DocumentId, bool useTabs, int width);
void setWrapIndent(DocumentId, WrapIndentMode); // where wrapped
WrapIndentMode wrapIndent(DocumentId) const; // continuation rows
// start: LeftMargin / SameAsLine / Indented
// Semantic classification (from the active colorizer's style rules;
// cheap -- no re-lex). CategorySpan = {start, length, SyntaxCategory}.
// The spell checker keeps to Comment/Documentation/String spans this way.
std::vector<CategorySpan> textCategories(DocumentId, start, end) const;
// Clipboard
std::string clipboardText() const;
Den blottlegger også fanegruppenes delte oppsett i backend-nøytrale vendinger
(groupCount(), groupOf(), groupWeights(), moveDocumentToGroup(),
setGroupWeights()), så et programtillegg kan fange og gjenopprette ruteoppstillinger
uten å se Qts deler. Se PluginApi/IEditorService.h for hele listen — hodefilen er den
avgjørende referansen.
Et dokument som vises i to ruter, er ett dokument med én DocumentId, så groupOf()
kan bare navngi den ene av plassene sine. To metoder beskriver og bygger opp igjen en
deling:
std::vector<int> groupsOf(DocumentId doc) const; // every pane showing it
bool showDocumentInGroup(DocumentId doc, int group); // put it in one more
showDocumentInGroup() gjør en visning som alt er i den ruten, aktiv, og lager ellers
enda en delt visning der — det samme som Vis ▸ Del dokumentet til høyre gjør, så det reises
ingen DocumentOpened, og begge visningene redigerer ett dokument. Ruter er det begrepet
programtillegg ser; enkelte visninger er ikke det, og det er derfor kallet navngir en
rute i stedet for å gi en visning tilbake.
Ta brukeren et sted¶
openFileAt(path, position) er det ene kallet bak «gå til byggefeilen», «hopp
til stakkrammen» eller «åpne søketreffet», så alle lander på samme måte. Det
åpner filen akkurat som openFile() (fanen til en fil som allerede er åpen,
aktiveres, den åpnes ikke en gang til), gjør den til det aktive dokumentet,
plasserer markøren uten merking, folder ut en bretting som skjuler linjen,
ruller linjen inn i visningen — sentrert når den var utenfor skjermen,
uten rulling når den allerede var synlig — og gir redigereren
tastaturfokus. Posisjonen avgrenses, avvises aldri: en linje forbi slutten
havner på siste linje, en kolonne forbi slutten av linjen havner på slutten av
den, negative verdier teller som 0. Det returnerer dokumentet markøren ble
plassert i, eller 0 når filen ikke kunne åpnes — og da har ingenting flyttet
seg.
revealLine(doc, line) viser et sted uten å ta brukeren dit: det folder ut og
ruller ruten som viser doc, men flytter ikke markøren eller merkingen,
bytter ikke fane og tar ikke fokus — et panel som forhåndsviser en stakkramme
eller følger sin egen merking, bruker det.
Dokumentets tegnkoding¶
Et dokuments tegnkoding på disken er kjernetilstand og ikke et programtilleggs. Verten
finner den når filen leses, og skriver den samme tegnkodingen tilbake ved hver lagring, så
en UTF-8-BOM-fil beholder merket sitt, og en UTF-16-fil blir værende UTF-16.
DocumentEncoding (PluginApi/DocumentEncoding.h) er en backend-uavhengig
POD-enumerasjon som dekker de fem viktigste Unicode- og ANSI-kodingene pluss de eldre
tegntabellene.
setDocumentEncoding() noterer et valg: den endrer ingen byte og avkoder ingenting på
nytt — bufferen er alt Unicode, og den nye tegnkodingen får virkning ved neste lagring. Et
programtillegg som tilbyr en «konverter til»-handling, bør derfor også kalle
setModified(doc, true); et som tilbyr «tolk som», laster selv teksten på nytt først og
noterer så valget. Verten publiserer DocumentEncodingChanged hver gang verdien faktisk
endrer seg. Et prosjekts .editorconfig med charset vinner fortsatt ved lagring.
Dokumentets linjeskift¶
Et dokuments linjeavslutning er også kjernetilstand. Verten finner den når filen
leses, skyver den inn i visningen så Enter setter inn den sekvensen, og skriver
bufferens byte tilbake ordrett — så en Unix-fil som redigeres på Windows, blir værende LF,
og en lagring uten endringer er byte for byte den samme. LineEnding
(PluginApi/LineEnding.h) er en backend-uavhengig POD-enumerasjon — Unknown / Lf /
CrLf / Cr — som navngir bytesekvensen og ikke en backend-konstant. Den leverer to
inline-hjelpere: lineEndingBytes() og platformLineEnding() (det en ny buffer begynner
med: CRLF på Windows, LF alle andre steder).
setLineEnding() noterer valget, stiller inn dokumentets visning så neste Enter setter
det inn, og avgjør hva neste lagring skriver. Den konverterer ingen eksisterende
tekst: et programtillegg som skriver om hele bufferen (kommandoene for konvertering av
linjeslutt), erstatter selv teksten først, så konverteringen blir værende én endring som
kan angres. Verten publiserer DocumentLineEndingChanged hver gang verdien faktisk endrer
seg. Et prosjekts .editorconfig med end_of_line vinner fortsatt ved lagring, og
flytter dokumentet over på den konvensjonen samtidig.
Tråder
Alle kall til konteksten og tjenestene skjer på vertens grensesnittråd. Kall ikke tjenester fra en bakgrunnstråd du selv har startet; kom tilbake først.
Versjonering¶
Kontrakten har en versjon i to deler, begge i PluginApi/PluginInfo.h:
constexpr std::uint32_t MTE_PLUGIN_API_VERSION = 1; // major
constexpr std::uint32_t MTE_PLUGIN_API_MINOR_VERSION = 0; // minor
Det som avgjør mellom dem, er hvem som implementerer grensesnittet:
| Endring | Type | Versjon |
|---|---|---|
En metode lagt til på slutten av et grensesnitt som bare redigereren implementerer, og som programtilleggene bare kaller (IEditorService, IPluginContext, hvert I…Registry, ILineMarkerService, …) |
forenlig | minor + 1 |
| Et nytt grensesnitt, en ny hendelsesstruktur, en ny enumerator på slutten av en enum | forenlig | minor + 1 |
Enhver endring av en struktur som krysser grensen (PluginInfo, ContextMenuRequest, DockPanelDescriptor, en hendelsesstruktur) |
uforenlig | major + 1 |
| En endret signatur, en fjernet eller omordnet metode, en ny overlasting av et eksisterende navn | uforenlig | major + 1 |
Enhver endring av et grensesnitt som et programtillegg implementerer (IPlugin, IHoverProvider, IContextMenuProvider, IDockPanel, ISettingsPage, …) |
uforenlig | major + 1 |
| En utgivelse bygget med en verktøykjede med et annet binært grensesnitt enn den forrige — en annen kompilatorfamilie eller et annet standardbibliotek, en ny hovedversjon av Qt | uforenlig | major + 1 |
Et nyere verktøysett fra den samme kompilatorfamilien, et annet nivå av C++-standarden og en nyere minor-versjon av Qt er ikke et slikt brudd og endrer ingen av tallene; se Byggekrav for hva et innebygd programtillegg må passe til.
Redigereren laster bare inn et programtillegg når
apiVersioniplugin.jsonogIPlugin::info().apiVersioner lik dens egen major, ogapiMinorVersioniplugin.json(valgfri, standard0) ogIPlugin::info().apiMinorVersionikke er høyere enn dens egen minor. Verdien iinfo()er som standard minor-versjonen til headerfilene programtillegget ble kompilert mot; et programtillegg som vet at det ikke bruker noe nyere, kan oppgi et lavere tall iplugin.json, og må likevel bestå den andre kontrollen.
Et avvist programtillegg står fortsatt oppført under Innstillinger ▸
Programtillegg med årsaken («Ikke kompatibel (API-en stemmer ikke)», «Krever
en nyere versjon av OctoEdit») og får en loggoppføring — aldri et krasj. Et
Python-programtillegg har ingen info() kompilert mot headerfilene, så
plugin.json er dets eneste erklæring: oppgi minor-versjonen til det nyeste
kallet du bruker.
Retningslinjer før utgivelsen
Før den første offentlige utgivelsen kan kontrakten fortsatt endre seg uten at
noen av tallene heves; fram til da står begge på 1 / 0. Fra den første
utgivelsen vurderes hver endring etter tabellen over.