Insticksmoduls-API:t — översikt¶
Alla typer i PluginApi bor i namespace MTE::plugin. De publika headerna får
inte inkludera någon header från Qt, Scintilla eller MTE:s inre — bara
standardbiblioteket. Det enda medvetna undantaget är den valfria mappen
PluginApi/Qt/ (PluginQtGlue.h, ISettingsPage.h, ThemeFormat.h) för
insticksmoduler som levererar egna Qt-widgetar.
Samma kontrakt uppfylls av två backendar: den inhemska Qt-laddaren (delade
bibliotek) och Python-backenden utanför processen
(Python-insticksmoduler). Det här avsnittet
dokumenterar C++-ytan; varje tjänst här som är backend-neutral har en
Python-motsvarighet på ctx.
En insticksmoduls livscykel¶
En inhemsk insticksmodul implementerar en enda klass:
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åste returnera sammaidochapiVersionsomplugin.json; värden dubbelkollar och vägrar läsa in vid avvikelse.initialize()körs en gång vid start (efter att varje insticksmodul upptäckts). Registrera kommandon, menyer, leverantörer och paneler här; returnerafalsebara vid ett verkligt misslyckande (värden läser ut modulen och noterar det).shutdown()körs vid avslut (eller när modulen läses ut). Den får inte kasta undantag;reset()varje symbol och nollställ dina sparade tjänstepekare.
RAII-symboler¶
Varje registrering returnerar en symbol (CommandToken, MenuToken, DockToken,
SettingsPageToken, Subscription, …) — en BasicToken som bara kan flyttas och
som äger registreringen: att förstöra den eller köra reset() på den återkallar
kommandot, menyposten, panelen eller prenumerationen. Håll varje symbol som medlem
och frigör den i shutdown(). Två åtkomstfunktioner spelar roll:
id()— en icke-förstörandeTokenId-vy, för API:er som adresserar posten utan att ta över den (till exempelIMenuRegistry::setItemChecked/setItemEnabled).release()— kopplar loss och returnerar id:t; symbolen återkallar inget längre. Sällan det du vill.
Tjänstesökaren¶
initialize() tar emot ett IPluginContext, en tjänstesökare som värden äger.
Spara inte kontexten bortom initialize(); spara de enskilda tjänstereferenser
du behöver (varje tjänst lever längre än varje insticksmodul).
| Åtkomst | Gränssnitt | Används för |
|---|---|---|
ctx.editor() |
IEditorService |
läsa/ändra buffert, markering, markör, filer, sökning, bokmärken, indrag, den delade vyns grupper |
ctx.commands() |
ICommandRegistry |
registrera / anropa användaråtgärder (med kortkommandon) |
ctx.menus() |
IMenuRegistry |
menyposter, avskiljare, kryssbara växlar, gråtona poster |
ctx.docks() |
IDockRegistry |
dockningsbara sidopaneler |
ctx.documents() |
IDocumentTypeRegistry |
fullvärdiga fliktyper i MainWindow (diff, loggvy, …) |
ctx.compare() |
ICompareService |
öppna jämförelsevyn med två rutor |
ctx.diagnostics() |
IDiagnosticsSink |
publicera våglinjer + marginaltecken |
ctx.events() |
IEventBus |
prenumerera på / publicera redigerarhändelser |
ctx.completions() |
ICompletionRegistry |
bidra med kandidater för automatisk komplettering |
ctx.contextMenu() |
IContextMenuRegistry |
dynamiska poster i redigerarens högerklicksmeny |
ctx.snippetSessions() |
ISnippetSessionService |
tillhandahålla kodfragment som utvidgas med Tab (värden kör fältsessionen) |
ctx.hover() |
IHoverRegistry |
verktygstips vid pekning |
ctx.signatureHelp() |
ISignatureHelpRegistry |
anropstips vid ( / , |
ctx.symbols() |
ISymbolRegistry |
symboler för dokument / arbetsyta (gå till symbol) |
ctx.languages() |
ILanguageRegistry |
lista språk, läsa / byta ett dokuments språk, bidra med en språkdefinition |
ctx.statusBar() |
IStatusBar |
flyktiga meddelanden + bestående segment |
ctx.settings() |
ISettings |
bestående nyckel/värde-konfiguration per insticksmodul |
ctx.appSettings() |
const ISettings& |
läsa programövergripande inställningar (AppSettingsKeys.h) |
ctx.documentSettings() |
IDocumentSettings |
konfiguration per dokument, skiktad med .editorconfig |
ctx.settingsRegistry() |
ISettingsRegistry |
lägga till en sida i dialogrutan Inställningar |
ctx.themePalette() |
IThemePalette |
namngivna temafärger, integration med Temaredigeraren |
ctx.lineMarkers() |
ILineMarkerService |
marginalsymboler och radtoningar som följer redigeringar (brytpunkter, ändringsremsor); anspråket på marginalklicket |
ctx.log() |
ILogger |
trace / debug / info / warning / error |
ctx.pluginDataDir() |
std::filesystem::path |
din egen skrivbara katalog |
ctx.diagnosticsDir() |
std::filesystem::path |
delad katalog för kraschartefakter och logg (skrivskyddad för insticksmoduler) |
Att utöka API:t senare innebär att lägga till ett nytt tjänstegränssnitt med tillhörande åtkomst — ett kompatibelt tillägg enligt versionsregeln: befintliga insticksmoduler fortsätter att läsas in. Den verktygskedja ett insticksmodulsbygge självt måste stämma med (kompilator, runtime, Qt-minor) listas under Skriva en insticksmodul, «Byggkrav».
IEditorService — dokumentmodellen¶
Redigeraren som insticksmoduler ser den — ingen Scintilla, ingen Qt. Dokument
refereras med ett ogenomskinligt DocumentId (std::uint64_t, 0 = ogiltigt).
Förskjutningar är förskjutningar i byte i UTF-8-texten; MatchRange är ett
halvöppet {start, end} där noMatch() betyder «hittades inte»;
EditorPosition är {line, column} räknat från 0.
// 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 exponerar också den delade vyns flikgruppslayout i backend-neutrala termer
(groupCount(), groupOf(), groupWeights(), moveDocumentToGroup(),
setGroupWeights()), så att en insticksmodul kan fånga och återställa
ruppställningar utan att se Qt:s delare. Se PluginApi/IEditorService.h för hela
listan — headern är den auktoritativa referensen.
Ett dokument som visas i två rutor är ett dokument med ett DocumentId, så
groupOf() kan bara namnge en av dess platser. Två metoder beskriver och bygger om
en delning:
std::vector<int> groupsOf(DocumentId doc) const; // every pane showing it
bool showDocumentInGroup(DocumentId doc, int group); // put it in one more
showDocumentInGroup() aktiverar en vy som redan finns i den rutan, och skapar
annars en andra delad vy där — samma sak som Visa ▸ Dela dokumentet höger
gör, så ingen DocumentOpened väcks och båda vyerna redigerar ett dokument. Rutor
är begreppet som vetter mot insticksmoduler; enskilda vyer är det inte, och därför
namnger anropet en ruta i stället för att lämna tillbaka en vy.
Ta användaren någonstans¶
openFileAt(path, position) är det enda anropet bakom «gå till byggfelet»,
«hoppa till stackramen» eller «öppna sökträffen», så att alla landar på samma
sätt. Det öppnar filen precis som openFile() (fliken för en redan öppen fil
aktiveras, den öppnas inte en gång till), gör den till det aktiva dokumentet,
placerar markören utan markering, fäller ut en vikning som döljer raden,
rullar raden in i vyn — centrerad om den var utanför skärmen, utan
rullning om den redan syntes — och ger redigeraren tangentbordsfokus.
Positionen begränsas, avvisas aldrig: en rad bortom slutet hamnar på sista
raden, en kolumn bortom radens slut hamnar i dess slut, negativa värden räknas
som 0. Det returnerar dokumentet där markören placerades, eller 0 om filen
inte kunde öppnas — och då har ingenting flyttats.
revealLine(doc, line) visar en plats utan att ta användaren dit: det fäller
ut och rullar rutan som visar doc, men flyttar inte markören eller
markeringen, byter inte flik och tar inte fokus — en panel som förhandsvisar
en stackram eller följer sin markering använder det.
Dokumentets teckenkodning¶
Ett dokuments teckenkodning på disken är kärnans tillstånd, inte en
insticksmoduls. Värden identifierar den när filen läses och skriver tillbaka samma
kodning vid varje sparande, så att en UTF-8-fil med BOM behåller sitt märke och en
UTF-16-fil förblir UTF-16. DocumentEncoding (PluginApi/DocumentEncoding.h) är
en backend-oberoende POD-uppräkning som täcker de fem huvudsakliga
Unicode/ANSI-kodningarna plus de äldre kodsidorna.
setDocumentEncoding() noterar ett val: den ändrar ingen byte och avkodar inget om
— bufferten är redan Unicode, och den nya kodningen får effekt vid nästa sparande.
En insticksmodul som erbjuder en åtgärd «konvertera till» bör därför också anropa
setModified(doc, true); en som erbjuder «tolka som» läser först om texten själv
och noterar sedan valet. Värden publicerar DocumentEncodingChanged när värdet
faktiskt ändras. Ett projekts charset i .editorconfig vinner ändå vid sparandet.
Dokumentets radslut¶
Ett dokuments radavslutare är kärnans tillstånd också. Värden identifierar det
när filen läses, skjuter in det i vyn så att Enter infogar den sekvensen, och
skriver tillbaka buffertens byte ordagrant — så att en Unix-fil som redigeras på
Windows förblir LF och ett sparande utan ändringar är byte för byte identiskt.
LineEnding (PluginApi/LineEnding.h) är en backend-oberoende POD-uppräkning —
Unknown / Lf / CrLf / Cr — som namnger bytesekvensen snarare än en konstant
i backenden. Den levererar två inline-hjälpare: lineEndingBytes() och
platformLineEnding() (det en ny buffert börjar med: CRLF på Windows, LF annars).
setLineEnding() noterar valet, stämmer om dokumentets vy så att nästa Enter
infogar det, och avgör vad nästa sparande skriver. Den konverterar ingen
befintlig text: en insticksmodul som skriver om hela bufferten (kommandona för
Konvertera radslut) ersätter texten själv först, så att konverteringen förblir en
enda ångerbar ändring. Värden publicerar DocumentLineEndingChanged när värdet
faktiskt ändras. Ett projekts end_of_line i .editorconfig vinner ändå vid
sparandet, och flyttar dokumentet till den konventionen med sig.
Trådar
Alla anrop till kontexten och tjänsterna sker på värdens gränssnittstråd. Anropa inte tjänster från en bakgrundstråd du startat; återvänd först.
Versionshantering¶
Kontraktet har en version i två delar, båda 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 avgör mellan dem är vem som implementerar gränssnittet:
| Ändring | Typ | Version |
|---|---|---|
En metod tillagd i slutet av ett gränssnitt som bara redigeraren implementerar och som insticksmodulerna bara anropar (IEditorService, IPluginContext, varje I…Registry, ILineMarkerService, …) |
kompatibel | minor + 1 |
| Ett nytt gränssnitt, en ny händelsestruktur, en ny enumerator i slutet av en enum | kompatibel | minor + 1 |
Varje ändring av en struktur som korsar gränsen (PluginInfo, ContextMenuRequest, DockPanelDescriptor, en händelsestruktur) |
inkompatibel | major + 1 |
| En ändrad signatur, en borttagen eller omordnad metod, en ny överlagring av ett befintligt namn | inkompatibel | major + 1 |
Varje ändring av ett gränssnitt som en insticksmodul implementerar (IPlugin, IHoverProvider, IContextMenuProvider, IDockPanel, ISettingsPage, …) |
inkompatibel | major + 1 |
| En utgåva byggd med en verktygskedja vars binära gränssnitt skiljer sig från den föregående — en annan kompilatorfamilj eller ett annat standardbibliotek, en ny huvudversion av Qt | inkompatibel | major + 1 |
En nyare verktygsuppsättning från samma kompilatorfamilj, en annan nivå av C++-standarden och en nyare minorversion av Qt är inget sådant brott och ändrar inget av talen; se Byggkrav för vad en inbyggd insticksmodul måste stämma med.
Redigeraren läser bara in en insticksmodul när
apiVersioniplugin.jsonochIPlugin::info().apiVersionär lika med dess egen major, ochapiMinorVersioniplugin.json(valfri, standard0) ochIPlugin::info().apiMinorVersioninte ligger över dess egen minor. Värdet iinfo()är som standard minorversionen för de headerfiler modulen kompilerades mot; en modul som vet att den inte använder något nyare får ange ett lägre tal iplugin.json, och måste ändå klara den andra kontrollen.
En avvisad insticksmodul står kvar i Inställningar ▸ Insticksmoduler med
orsaken («Inkompatibel (API stämmer inte)», «Kräver en nyare version av
OctoEdit») och får en loggpost — aldrig en krasch. En Python-modul har ingen
info() kompilerad mot headerfilerna, så dess plugin.json är dess enda
uppgift: ange minorversionen för det nyaste anropet du använder.
Policy före utgåvan
Före den första publika utgåvan kan kontraktet fortfarande ändras utan att
något av talen höjs; fram till dess står båda på 1 / 0. Från den
första utgåvan bedöms varje ändring enligt tabellen ovan.