Modular Text Editorin Python-liitännäisen kirjoittaminen Clauden kanssa¶
Kohdeyleisö: tekoälyavustaja (Claude, ChatGPT ja muut). Tämä sivu on itsenäinen ohjejoukko: anna se avustajalle yhdessä tehtävän kanssa (»kirjoita minulle liitännäinen, joka tekee X«), niin avustajan tulisi tuottaa täydellinen, oikea ja toimiva liitännäiskansio ilman että sen tarvitsee lukea muuta dokumentaatiota.
Jos olet ihminen, joka yrittää kirjoittaa liitännäisen käsin, aloita täältä — tämä sivu on optimoitu tekoälyn luettavaksi ja ohittaa ystävälliset esimerkit.
1. Mitä tuotat¶
Kansion, jossa on täsmälleen kaksi tiedostoa (plus valinnaisia lisiä):
Palauta kansio yhtenä koodilohkona tiedostoa kohti. Käyttäjä joko pudottaa kansion editorin
liitännäishakemistoon tai pakkaa kansion zipiksi, nimeää arkiston uudelleen muotoon
<name>.mteplugin ja asentaa sen kohdasta Asetukset → Liitännäiset → Asenna liitännäinen....
Molemmat tavat vaativat uudelleenkäynnistyksen latautuakseen.
Nämä kaksi tiedostoa on kuvattu tyhjentävästi alla. Noudata sääntöjä kirjaimellisesti — isäntä tekee staattisen tarkistuksen ja hylkää liitännäiset, jotka rikkovat niitä.
2. plugin.json — kanoninen mallipohja¶
{
"id": "org.example.<short-name>",
"name": "<Human Display Name>",
"version": "1.0.0",
"vendor": "<Author or Team>",
"description": "<One sentence about what it does.>",
"apiVersion": 1,
"entry": "python",
"module": "main",
"order": 900,
"menus": [
{ "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
]
}
Ehdottomat säännöt¶
"apiVersion"täytyy olla1(isännän nykyinen versio)."entry"täytyy olla"python"— mikään muu ei reitity tähän taustajärjestelmään."module"täytyy olla"main", ellei ole nimenomaista syytä nimetä moduulia toisin — eikä koskaan pistein erotettua polkua (vaihe 1 tukee vain litteitä moduulinimiä)."id"täytyy olla käänteisen DNS:n tyylinen (org.<vendor>.<name>) ja globaalisti yksilöllinen käyttäjän asennettujen liitännäisten kesken. Jos käyttäjä ei määrittänyt toimittajaa, käytä muotoaorg.example..menus[]täytyy sisältää vähintään yksi kohta, jonkatitlevastaa jokaisen tiedostossamain.pykäyttämäsi valikkopolun vasemmanpuoleisinta osaa. Suosi arvoa"Plugins"kaikelle käyttäjälle näkyvälle, mikä ei selvästi kuulu ytimen valikkoon.
Valinnaiset kentät¶
"pyRequires": []— taulukko pip-vaatimusmerkkijonoja (esimerkiksi["requests>=2.31"]). Kun se ei ole tyhjä, editori valmistelee liitännäiskohtaisen virtuaaliympäristön ensimmäisellä käynnistyksellä (pip install— vaatii verkon tuon yhden kerran) ja ajaa liitännäisen sen alla. Suosi standardikirjastoa, kun se hoitaa työn (ei valmisteluviivettä, toimii verkotta); ilmoitapyRequires, kun tehtävä aidosti tarvitsee kolmannen osapuolen paketin, ja kerro käyttäjälle, että ensimmäinen käynnistys pysähtyy asentamaan sen."permissions": []— karkeita kykyilmoituksia (esimerkiksi["network"]), näytettynä vain luettavana liitännäissivulla. Ilmoita"network", kun liitännäinen tekee ulospäin suuntautuvia kutsuja; se on rehellistä merkintää käyttäjälle eikä pakotusta."requires": []— MUIDEN liitännäisten tunnisteet, joita ilman tämä ei voi toimia (ei pip-paketteja). Ne käynnistyvät ensin; jos jokin puuttuu tai ei ole käytössä, tämä liitännäinen ei käynnisty, ja liitännäissivu kertoo miksi. Jätä tyhjäksi, ellei käyttäjän tehtävä todella riipu toisesta liitännäisestä."apiMinorVersion": 0— editorin API:n pienin minor-versio, jota liitännäinen tarvitsee. Jätä arvoksi0, ellet käytä kutsua, joka on dokumentoitu uudemmaksi.
Arvot, joita sinun EI pidä keksiä¶
- Mikä tahansa kenttä, jota ei ole lueteltu yllä. Isäntä ohittaa tuntemattomat avaimet; niiden lisääminen on ylläpitoriski.
3. main.py — kanoninen runko¶
"""<Short description of what this plugin does.>"""
import mte # noqa: F401 -- pulls in dataclasses for event payloads
def register(ctx):
# Register commands, menu items, and event subscriptions here.
# Everything below is illustrative -- delete what you don't need.
@ctx.commands.command(id="<id>.<verb>",
title="<Human Label>",
shortcut="Ctrl+Alt+<letter>", # optional
category="<Group>") # optional
def _handler():
ctx.status.show("hello", timeout_ms=2000)
ctx.log.info("<id>: handler ran")
ctx.menus.add_item("Plugins/<Human Label>/<Item>", "<id>.<verb>")
Ehdottomat säännöt¶
register(ctx)TÄYTYY olla ylätason funktio. Staattinen tarkistus vaatii rivin, joka alkaa merkkijonolladef register(sarakkeesta 0. Jos sijoitat sen luokan sisään, koristelet sen moduulitasolla tai teet siitäasync def-funktion, liitännäinen hylätään hiljaisesti etsintähetkellä.- Tuo moduulista
mte, kun tilaat tapahtumia — tyypitetyt dataluokat (mte.DocumentSavedja muut) tulevat tuosta paketista.import mteylhäällä on turvallista, vaikket käyttäisi sitä suoraan; yllä oleva# noqa: F401estää lint-melun. - Älä koskaan käytä funktiota
print(). Työntekijän stdout on RPC-kanava; siihen kirjoittaminen turmelee protokollan. Käytä funktioitactx.log.info / warning / error. - Älä koskaan tuo mitään, mitä et ole ilmoittanut. Standardikirjaston ja moduulin
mtelisäksi jokaisen kolmannen osapuolen tuonnin täytyy esiintyä tiedostonplugin.jsonkentässäpyRequires(editori asentaa sen pipillä liitännäisen virtuaaliympäristöön ensimmäisellä käynnistyksellä) tai olla liitetty liitännäiskansioon. Ilmoittamaton tuonti nostaa poikkeuksenModuleNotFoundErrorkäyttäjän koneella. - Komento- ja tapahtumakäsittelijät saavat estää hetkeksi — ne ajetaan työntekijän
säiepoolissa, joten synkroniset kutsut kohteisiin
ctx.editor,ctx.settingsjactx.data_dirovat sallittuja. Mutta täydennystoimittajan takaisinkutsut (@ctx.completions.provider(...)) EIVÄT SAA estää: ne ajetaan tapahtumasilmukassa noin 30 ms:n määräajalla. Ei I/O:ta, ei kutsuja kohteeseenctx.editor.*, vain välimuistia. Rakenna hakemistot muualla (esimerkiksi tapahtumassadocument.opened) ja vastaa sanakirjasta. - Älä tuo Qt:tä, PySideä, PyQt:tä tai Scintillaa. Ne eivät ole käytettävissä työntekijässä.
- Komentotunnisteilla täytyy olla etuliite, joka on liitännäisesi nimiavaruus, jotta
vältetään törmäykset ytimen ja muiden liitännäisten kanssa.
<id>.<verb>yllä; esimerkiksiwordcount.counttaihello.sayHi. - Valikkopolkujen täytyy alkaa ylimmän tason valikolla, jonka olet myös ilmoittanut
tiedoston
plugin.jsonkentässämenus[]. Tapa:"Plugins/<Plugin Name>/<Verb>".
4. Koko API — kaikki kohteessa ctx¶
Sinulla on kahdeksantoista palvelua: commands, menus, events, log, status, editor,
languages, settings, app_settings, document_settings, theme, diagnostics, compare,
completions, docks, settings_page, ui sekä data_dir()/diagnostics_dir(). Mitään, mitä ei
ole lueteltu täällä, ei ole olemassa — älä keksi palveluita. (Liitännäiskohtaiset
virtuaaliympäristöt ilmoitetaan kentässä pyRequires eikä ctx-palveluna; virtaavalla ja
peruutettavalla protokolla-alustalla ei ole vielä liitännäisille näkyvää API:ta.)
ctx.commands¶
@ctx.commands.command(*, id, title, shortcut="", category="",
requires_editor=False, requires_writable_editor=False)
def _handler(): ...
Koriste. Rekisteröi komennon ja sitoo funktion _handler (nollaparametrinen funktio, joka
palauttaa arvon None). Työntekijä nappaa funktion _handler poikkeukset ja kirjoittaa ne
stderriin; ne eivät kaada editoria.
Aseta requires_editor, kun komento lukee aktiivista asiakirjaa (puskuri, valinta,
kohdistin), ja requires_writable_editor, kun se muuttaa tekstiä. Isäntä harmaannuttaa
silloin komennon siellä, missä se ei voisi toimia — välilehdellä, jolla ei ole asiakirjaa
(vertailu- tai lokinäkymä), ja kirjoituslipun osalta vain luettavassa asiakirjassa — sen
sijaan että antaisi käyttäjän käynnistää hiljaisen tyhjäkäynnin. Kirjoituslippu sisältää
toisen; jätä molemmat pois komennoilta, jotka vain avaavat ikkunan tai paneelin.
ctx.menus¶
ctx.menus.add_item(path, command_id) # -> handler_id (str)
ctx.menus.add_separator(path) # -> handler_id (str)
ctx.menus.set_item_checked(handler_id, checked) # -> None
ctx.menus.set_item_enabled(handler_id, enabled) # -> None
path on "Ylin/Ali/…/Nimiö". Välialivalikot luodaan tarvittaessa.
set_item_checked muuttaa kohdan valittavaksi valikkokohdaksi ja asettaa sen merkin. Käytä
sitä pysyviin päälle/pois-kytkimiin. Ensimmäinen kutsu muuttaa kohdan; seuraavat kutsut vain
kääntävät. Hiljainen tyhjäkäynti tuntemattomille tunnisteille.
set_item_enabled harmaannuttaa kohdan. Käytä sitä, kun komento tarvitsee edellytyksen,
jotta käyttämätön kohta näyttää käyttämättömältä sen sijaan että se ei tekisi mitään
napsautettaessa. Idempotentti; kohdat alkavat käytössä olevina; hiljainen tyhjäkäynti
tuntemattomille tunnisteille.
ctx.events¶
Seitsemäntoista aihetta, kukin kevyellä koristeella ja raa'alla muodolla.
| Kevyt muoto | Raaka aihe | Hyötykuorma |
|---|---|---|
@ctx.events.on_document_opened |
document.opened |
mte.DocumentOpened(document: int, path: str) |
@ctx.events.on_document_closed |
document.closed |
mte.DocumentClosed(document: int) |
@ctx.events.on_document_saved |
document.saved |
mte.DocumentSaved(document: int) |
@ctx.events.on_document_modified_changed |
document.modifiedChanged |
mte.DocumentModifiedChanged(document: int, modified: bool) |
@ctx.events.on_document_encoding_changed |
document.encodingChanged |
mte.DocumentEncodingChanged(document: int, encoding: mte.DocumentEncoding) |
@ctx.events.on_document_line_ending_changed |
document.lineEndingChanged |
mte.DocumentLineEndingChanged(document: int, ending: mte.LineEnding) |
@ctx.events.on_text_changed |
text.changed |
mte.TextChanged(document, position, added_bytes, removed_bytes) — jokaisella muokkauksella, pidä se hyvin yksinkertaisena |
@ctx.events.on_selection_changed |
selection.changed |
mte.SelectionChanged(document: int) |
@ctx.events.on_active_document_changed |
document.activeChanged |
mte.ActiveDocumentChanged(document: int) |
@ctx.events.on_settings_changed |
settings.changed |
mte.SettingsChanged(scope: str) |
@ctx.events.on_theme_changed |
theme.changed |
mte.ThemeChanged(variant: str) |
@ctx.events.on_workspace_root_changed |
workspace.rootChanged |
mte.WorkspaceRootChanged(root: str) |
@ctx.events.on_workspace_files_changed |
workspace.filesChanged |
mte.WorkspaceFilesChanged(files: list[mte.WorkspaceFile]) |
@ctx.events.on_command_shortcut_changed |
command.shortcutChanged |
mte.CommandShortcutChanged(command_id: str, sequence: str) |
@ctx.events.on_host_window_shown |
host.windowShown |
mte.HostWindowShown() — kerran ajoa kohti, ensimmäisen ruudun jälkeen |
@ctx.events.on_app_about_to_quit |
app.aboutToQuit |
mte.AppAboutToQuit() |
@ctx.events.on_margin_clicked |
margin.clicked |
mte.MarginClicked(document: int, line: int, margin: str, modifiers: int) — napsautus kirjanmerkkisarakkeessa; modifiers-bitit: mte.MODIFIER_* |
Raaka muoto:
handler_id = ctx.events.subscribe("<topic>", callback)
ctx.events.unsubscribe(handler_id) # to unbind
Älä keksi uusia aiheita — raaka muoto tarkistaa ne ja heittää poikkeuksen ValueError
kaikesta, mitä ei ole taulukossa.
ctx.log¶
Lähetä ja unohda. Rivit saavat etuliitteen [python:<plugin.id>] isännän
diagnostiikkalokissa.
ctx.status¶
ctx.status.show(message: str, timeout_ms: int = 0, level: str = "info")
ctx.status.clear()
seg = ctx.status.add_segment(id: str, priority: int = 100) # persistent
seg.set_text(str); seg.set_visible(bool); seg.remove()
level täytyy olla "info" / "warning" / "error". timeout_ms=0 tarkoittaa, että
isäntä valitsee oletuksen. show() on ohimeneviin ilmoituksiin; kaikki, mikä jää näytölle
(elävä laskuri, tilalippu), on osio — nämä kaksi eivät koskaan tuhoa toisiaan.
ctx.editor (vaihe 2)¶
Kukin metodi on synkroninen: se estää kutsuvan säikeen. Sallittua def-tyyppisestä
komento- tai tapahtumatakaisinkutsusta (työntekijä jakaa synkroniset käsittelijät
säiepooliin); kiellettyä async def-käsittelijöistä ja kohteen ctx.completions
toimittajista (jotka ajetaan suoraan tapahtumasilmukassa).
Asiakirjoihin viitataan läpinäkymättömillä int-tunnisteilla (0 = ei asiakirjaa).
doc = ctx.editor.active_document()
docs = ctx.editor.open_documents()
ctx.editor.set_active_document(doc)
path = ctx.editor.file_path(doc) # str or None
text = ctx.editor.text(doc)
slice = ctx.editor.text_range(doc, start, end) # [start, end) bytes
ctx.editor.set_text(doc, "new text")
n = ctx.editor.length(doc)
sel = ctx.editor.selection_range(doc) # mte.MatchRange
s = ctx.editor.selected_text(doc)
ctx.editor.replace_selection(doc, "hi")
ctx.editor.set_selection(doc, mte.MatchRange(4, 9))
pos = ctx.editor.caret(doc) # mte.EditorPosition
ctx.editor.set_caret(doc, mte.EditorPosition(line=5, column=0))
new_doc = ctx.editor.open_file("/path/to/file") # 0 on failure
here = ctx.editor.open_file_at("/path/to/file", mte.EditorPosition(41, 0)) # open + caret + scroll + focus
ctx.editor.reveal_line(doc, 120) # scroll only; caret and focus stay
ok = ctx.editor.save_document(doc)
closed = ctx.editor.close_document(doc) # prompts if modified; False = cancelled
dirty = ctx.editor.is_modified(doc)
hit = ctx.editor.find_in_range(doc, "TODO", 0,
ctx.editor.length(doc),
mte.SearchOptions(match_case=True))
new_end = ctx.editor.replace_target(doc, hit, "DONE",
mte.SearchOptions())
style = ctx.editor.indentation(doc) # IndentationStyle
ctx.editor.set_indentation(doc,
mte.IndentationStyle(use_tabs=False, width=2))
ctx.editor.set_wrap_indent(doc, "same") # "left" | "same" | "indent"
lang = ctx.editor.colorizer_id(doc) # "python", "cpp", ...
mte.SearchOptions.mode: "normal" / "extended" / "regex".
mte.SearchOptions.direction: "forward" / "backward".
ctx.settings / ctx.app_settings (vaihe 2)¶
Tyypitetty avain–arvo-varasto liitännäiskohtaisesti rajattuna (ctx.settings) sekä vain
luettava näkymä sovelluksenlaajuisiin asetuksiin (ctx.app_settings). Tyyppi päätellään
oletusarvon Python-tyypistä.
ctx.settings.set("wrap", True)
ctx.settings.set("count", 42)
ctx.settings.set("theme", "dark")
wrap = ctx.settings.get("wrap", False) # bool
count = ctx.settings.get("count", 0) # int
theme = ctx.settings.get("theme", "light") # str
ctx.settings.contains("wrap")
ctx.settings.remove("stale")
# App-wide (read-only)
tw = ctx.app_settings.get("editor.tabWidth", 4)
ctx.app_settings.set/remove/sync heittävät poikkeuksen RuntimeError. Bool täytyy
tarkistaa ennen int-tyyppiä omassa logiikassasi lukiessasi (Pythonissa
isinstance(True, int) == True).
ctx.completions (vaihe 2)¶
@ctx.completions.provider(languages=["python"])
def _complete(req: mte.CompletionRequest):
return [mte.CompletionItem(text=w, kind=mte.KIND_KEYWORD)
for w in ("hello", "hello_world")
if w.startswith(req.prefix)]
Olion req kentät: document, prefix, position, language_id, manual. Lajit:
KIND_KEYWORD / KIND_WORD / KIND_SYMBOL / KIND_SNIPPET / KIND_UNKNOWN. Palauta
list[CompletionItem], tavallisia merkkijonoja tai sanakirjoja. Takaisinkutsun TÄYTYY
palata noin 30 ms:n sisällä, muutoin isäntä pudottaa tuloksesi tuon näppäinpainalluksen
osalta — ei I/O:ta, ei kutsua ctx.editor.text(...), vain välimuistia. Rakenna hakemistot
muualla (esimerkiksi tapahtumassa document.opened).
ctx.data_dir() / ctx.diagnostics_dir() (vaihe 2)¶
Käytä funktiota data_dir() välimuisteihin ja hakemistoihin. diagnostics_dir() on vain
luettava (kaatumisraportoija kirjoittaa sinne).
ctx.docks — kuvailevat telakkapaneelit (vaihe 3)¶
from mte.ui import Column, Row, Label, LineEdit, Checkbox, Button
def _on_needle_changed(value: str) -> None:
ctx.log.info(f"needle -> {value!r}")
needle_handler = ctx.docks.on_change(_on_needle_changed)
panel_id = ctx.docks.add_panel(
id="myplugin.panel", # returned; use it for ctx.ui.get / ctx.ui.set
title="My Plugin",
area="right", # "left" / "right" / "top" / "bottom"
form=Column([
Label(text="Search:"),
LineEdit(id="needle", placeholder="text...",
binds="settings:lastNeedle",
on_change=needle_handler),
Checkbox(id="cs", label="Case sensitive",
binds="settings:caseSensitive"),
Row([Button(text="Run", on_click="myplugin.run")]),
]),
)
Rakenna lomakemäärittely mte.ui-apureilla. Ohjausobjektisanasto on kiinteä:
Label / Button / Checkbox / LineEdit / NumberSpin / ComboBox / List rakenteiden
Column / Row / Group sisällä.
binds="settings:<key>"— ohjausobjektin arvo tallentuu automaattisesti kohteenctx.settingskautta (sama varasto kuinctx.settings.set).on_click="<cmd.id>"— vain painikkeet; kutsuu rekisteröityä komentoa.on_change=<handler>— varaa funktiollactx.docks.on_change(callback); laukeaa käyttäjän muokkauksilla.
on_change-takaisinkutsut ajetaan työntekijän tapahtumasilmukassa (EI säiepoolissa). Pidä
ne halpoina — ei editorikutsuja, ei pitkää työtä. Muuta pientä muistissa olevaa tilaa,
kirjaa lokiin tai laukaise komento; tee raskas työ komennosta.
Anna initially_visible=False paneelille, joka ei saa ponnahtaa esiin käynnistyksessä, ja
tuo se sitten esiin komennostaan kutsulla ctx.docks.set_panel_visible(panel_id, True)
(joka myös nostaa sen etualalle; idempotentti; tuntemattomat tunnisteet ohitetaan).
ctx.docks.is_panel_visible(panel_id) kertoo, onko paneeli auki (taustalla oleva
välilehti lasketaan). Useita yhteen kuuluvia paneeleita: anna sama
tab_group="<plugin.id>/panels" ja sama area, niin ne jakavat yhden
välilehdin varustetun telakan.
ctx.line_markers — merkit asiakirjan riveillä¶
kind = ctx.line_markers.register_kind(glyph=mte.GLYPH_CIRCLE,
glyph_color="<category id>", # from ctx.theme
line_tint="", z_order=0) # RpcError when no slot is left
m = ctx.line_markers.add_marker(kind, doc, line) # 0 = refused; follows edits
ctx.line_markers.line_of(m) # -1 = gone (document closed)
ctx.line_markers.markers(kind, doc) # [mte.LineMarkerPlacement(id, line)]
ctx.line_markers.remove_marker(m)
claim = ctx.line_markers.claim_margin_click() # stop bookmark toggling on margin clicks
Rekisteröi lajit kerran funktiossa register(); editorissa on vain muutama
paikka. Varaa marginaalin napsautus vain, kun liitännäinen antaa sille
merkityksen — napsautukset saapuvat joka tapauksessa koristeen
@ctx.events.on_margin_clicked kautta.
ctx.languages — syntaksivärjäys¶
ctx.languages.available() # list[LanguageInfo(id, name)]
ctx.languages.language_for(doc) # "" when uncoloured
ctx.languages.set_language_for(doc, "json") # "" clears; False = unknown id
ctx.languages.auto_detect_for(doc)
h = ctx.languages.register(id=, name=, lexer=, extensions=[...],
keywords=[{"group": ..., "words": ...}],
styles={"<LEXER STYLE NAME>": "comment", ...})
ctx.languages.unregister(h) # restores what it displaced
lexer täytyy nimetä olemassa oleva taustajärjestelmän leksikaalinen jäsennin; styles ja
keywords käyttävät jäsentimen omia nimiä (sama muoto kuin mukana tulevissa
colorizers/*.json-tiedostoissa), eivät koskaan numeerisia tunnisteita.
ctx.document_settings / mte.keys¶
ctx.document_settings.get_bool(doc, key, default) # .editorconfig-layered, read-only
ctx.document_settings.get_int / get_double / get_string / contains
ctx.app_settings.get(mte.keys.EDITOR_FONT_SIZE, mte.keys.DEFAULT_EDITOR_FONT_SIZE)
ctx.diagnostics — aaltoviivat¶
ctx.diagnostics.publish(doc, source: str, [mte.Diagnostic(start=, end=,
message=, severity=mte.SEVERITY_WARNING, code="")])
ctx.diagnostics.publish(doc, source, []) # retract only your source
ctx.diagnostics.clear(doc) # clears EVERY producer's set
Lähetä KOKO joukko lähteellesi joka kerta; se korvaa edellisen. source on pakollinen.
ctx.theme — teemoitettavat värit omaan käyttöliittymääsi¶
h = ctx.theme.register_category(id, display_label, default_light=0x..., default_dark=0x...)
rgb = ctx.theme.resolve_color(id) # 0xRRGGBB; re-read on on_theme_changed
ctx.theme.has_override(id); ctx.theme.active_variant(); ctx.theme.categories()
ctx.compare — vierekkäinen vertailu¶
ctx.compare.open(mte.CompareSource(title, path=..., content=...),
mte.CompareSource(...), ignore_whitespace=False,
ignore_case=False, ignore_eol=False) # -> bool
ctx.events.publish¶
Vain aiheet workspace.rootChanged / workspace.filesChanged ovat liitännäisen
julkaistavissa; mikä tahansa muu heittää poikkeuksen ValueError. Jokainen isäntätapahtuma
paitsi document.aboutToSave on tilattavissa (katso koristeluettelo);
natiivilla tapahtumalla DocumentAboutToClose ei ole lainkaan Python-aihetta —
käytä aihetta document.closed.
ctx.settings_page — kuvailevat asetussivut (vaihe 3)¶
from mte.ui import Column, Checkbox, NumberSpin
ctx.settings_page.register(
category="Plugins/My Plugin", # slash-separated tree path
title="My Plugin",
form=Column([
Checkbox(id="verbose", label="Verbose logging",
binds="settings:verbose"),
NumberSpin(id="cap", default=100, min=1, max=10_000,
binds="settings:cap"),
]),
)
Samat rakentajat, sama binds-semantiikka. Kaksi ohjausobjektia, jotka jakavat arvon
binds="settings:<key>", peilaavat toisiaan reaaliajassa — telakkapaneelilla ja
asetussivulla voi molemmilla olla valintaruutu samalle asetukselle, ja toisen kytkeminen
kääntää toisen.
Vaiheen 3 varauma
Arvot tallentuvat innokkaasti jokaisella käyttäjän muokkauksella — Peruuta ei peru sitä, mitä käyttäjä muutti. Tämä paranee tulevassa hienosäädössä.
ctx.ui.get / ctx.ui.set — ajonaikainen pääsy paneelien ohjausobjekteihin (vaihe 3)¶
# Read the current value; returns str or None.
current = ctx.ui.get(panel_id, "needle")
# Write. Returns bool (False when panel/control unknown, or when the
# value can't be coerced to the target type).
ctx.ui.set(panel_id, "needle", "hello")
ctx.ui.set(panel_id, "cs", True) # bool -> "true"/"false"
get palauttaa ensisijaisen arvon merkkijonona:
- Label / Button / LineEdit / ComboBox → teksti
- Checkbox →
"true"/"false" - NumberSpin → kokonaisluku desimaalina
- List → valitun kohdan teksti (tyhjä merkkijono, jos ei mitään)
Isännän ohjaama set on hiljainen: se EI laukaise ohjausobjektin on_change-kutsua
uudelleen, joten voit päivittää omia ohjausobjektejasi komentokäsittelijästä ilman
äärettömiä silmukoita.
5. Täysi läpikäyty esimerkki — wordcount-liitännäinen¶
Kopioi tämä sanatarkasti ja muuta kentät id, name ja otsikot sopimaan tapaukseesi. Se
käyttää jokaista vaiheen 1 palvelua.
{
"id": "org.example.wordcount",
"name": "Word Count",
"version": "1.0.0",
"vendor": "Example, Inc.",
"description": "Counts words the user has typed since launch and logs saves.",
"apiVersion": 1,
"entry": "python",
"module": "main",
"order": 900,
"menus": [
{ "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
]
}
"""Reports a session word-count total via a menu command and logs saves."""
import mte # noqa: F401
# Module-level state is fine -- one worker per plugin, one register() call.
_state = {"saves": 0}
def register(ctx):
ctx.log.info("wordcount: register() ran")
@ctx.commands.command(id="wordcount.report",
title="Report save count",
shortcut="Ctrl+Alt+W",
category="Text")
def _report():
n = _state["saves"]
ctx.status.show(f"{n} save(s) this session", timeout_ms=2500)
ctx.log.info(f"wordcount: user asked for count; {n} saves so far")
ctx.menus.add_item("Plugins/Word Count/Report save count",
"wordcount.report")
@ctx.events.on_document_saved
def _saved(ev: mte.DocumentSaved):
_state["saves"] += 1
ctx.log.info(f"wordcount: doc {ev.document} saved -- total now {_state['saves']}")
@ctx.events.on_app_about_to_quit
def _quit(_ev):
ctx.log.info(f"wordcount: shutting down after {_state['saves']} saves")
Käyttäjän odotettu kulku: avaa editori, tallenna tiedosto kolme kertaa ja napsauta sitten
Plugins → Word Count → Report save count. Tilarivillä näkyy
3 save(s) this session. Loki kirjaa jokaisen vaiheen.
6. Yleiset tehtävät — kopioitavat reseptit¶
Kukin resepti on pelkkä runko: liitä se funktion def register(ctx): sisään.
Valikkokomennon lisääminen¶
@ctx.commands.command(id="mine.hello", title="Say Hi", shortcut="Ctrl+Alt+Y")
def _hi():
ctx.status.show("hi")
ctx.menus.add_item("Plugins/Mine/Say Hi", "mine.hello")
Tallennukseen reagoiminen¶
Varoittaminen lopetettaessa¶
@ctx.events.on_app_about_to_quit
def _on_quit(_ev):
ctx.log.warning("mine: last chance to persist state")
Kirjaaminen tietylle tasolle¶
ctx.log.trace ("very fine")
ctx.log.debug ("dev")
ctx.log.info ("routine")
ctx.log.warning("odd")
ctx.log.error ("wrong")
Valikkoerotin¶
Useita toisiinsa liittyviä komentoja¶
@ctx.commands.command(id="mine.a", title="Do A")
def _a(): ...
@ctx.commands.command(id="mine.b", title="Do B")
def _b(): ...
ctx.menus.add_item("Plugins/Mine/Do A", "mine.a")
ctx.menus.add_item("Plugins/Mine/Do B", "mine.b")
Pysyvä päälle/pois-kytkin näkyvällä valintamerkillä¶
KEY = "verbose"
item = ctx.menus.add_item("View/Verbose Logging", "mine.toggleVerbose")
# Seed the mark from the persisted state so it matches on launch.
ctx.menus.set_item_checked(item, ctx.settings.get(KEY, False))
@ctx.commands.command(id="mine.toggleVerbose", title="Verbose Logging")
def _toggle():
on = not ctx.settings.get(KEY, False)
ctx.settings.set(KEY, on)
ctx.menus.set_item_checked(item, on)
Sivupaneeli, jossa on tekstinsyöttö, valintaruutu ja kaksi painiketta¶
from mte.ui import Column, Row, Label, LineEdit, Checkbox, Button
def _on_needle(value): ctx.log.info(f"needle={value!r}")
needle_h = ctx.docks.on_change(_on_needle)
panel = ctx.docks.add_panel(
id="mine.panel", title="Mine", area="right",
form=Column([
Label(text="Search:"),
LineEdit(id="needle", placeholder="text...",
binds="settings:needle", on_change=needle_h),
Checkbox(id="cs", label="Case", binds="settings:cs"),
Row([Button(text="Run", on_click="mine.run"),
Button(text="Clear", on_click="mine.clear")]),
]),
)
@ctx.commands.command(id="mine.run", title="Run")
def _run():
needle = ctx.ui.get(panel, "needle") or ""
cs = ctx.ui.get(panel, "cs") == "true"
ctx.status.show(f"searching for {needle!r} (case={cs})")
@ctx.commands.command(id="mine.clear", title="Clear")
def _clear():
ctx.ui.set(panel, "needle", "")
Asetussivu, joka peilaa paneelin asetuksia¶
from mte.ui import Column, Checkbox
# Both controls bind to the SAME settings key -- toggling one
# flips the other in real time.
ctx.docks.add_panel(id="mine.panel", title="Mine", area="right",
form=Column([Checkbox(id="cs", label="Case",
binds="settings:cs")]))
ctx.settings_page.register(category="Plugins/Mine", title="Mine",
form=Column([Checkbox(id="cs", label="Case sensitive",
binds="settings:cs")]))
7. Antimallit — ÄLÄ tee näitä¶
print() diagnostiikkaan¶
Väärin:
Oikein:
register luokan sisällä¶
Väärin:
Oikein:
async def register¶
Väärin:
Oikein:
Kolmannen osapuolen tuonnit ilman wheel-tiedoston toimittamista¶
Väärin:
Oikein (vaihe 1): pysy standardikirjastossa. Jos todella tarvitset jotain kolmannelta
osapuolelta, toimita wheel liitännäiskansion sisällä ja laajenna polkua sys.path
tiedoston main.py ensimmäisillä riveillä; katso Paketointi.
Qt-tuonnit¶
Väärin:
Oikein: ei Qt:tä missään. Kaikki käyttöliittymä, jota voit tarjota, kulkee kohteiden
ctx.menus, ctx.status, ctx.diagnostics, ctx.compare sekä kuvailevien telakka- ja
asetuspaneelien kautta. Ei koskaan raakaa ohjausobjektia.
Estäminen käsittelijässä¶
Väärin:
def _handler():
time.sleep(30) # <-- stalls the worker for 30s
result = requests.get("https://slow.example")
Oikein (vaihe 1): siirrä se säikeeseen ja viesti kohteen ctx.log kautta:
def _handler():
import threading
def _work():
time.sleep(30)
ctx.log.info("done")
threading.Thread(target=_work, daemon=True).start()
ctx.status.show("started")
Valikkopolku, jonka ylin osa ei ole tiedostossa plugin.json¶
Väärin:
Oikein: pidä polun ylin osa linjassa kentän menus[] kanssa. Käytä yllä muotoa
"Plugins/Mine/Do".
Olion ctx säilyttäminen myöhempää käyttöä varten¶
Väärin:
_ctx = None
def register(ctx):
global _ctx
_ctx = ctx # <-- fine for the lifetime of this worker,
# but confusing and unnecessary
def some_other_function():
_ctx.log.info("...") # <-- called from where?
Oikein: kaappaa tarvitsemasi palvelut siihen sulkeumaan, joka niitä käyttää. register on
se paikka, jossa kytkennät tehdään; kaikki muu on käsittelijä, jolla on jo pääsy sulkeuman
kautta.
8. Vianetsintälista käyttäjälle¶
Sisällytä tämä vastauksesi loppuun, kun tuotat liitännäisen, jotta käyttäjä tietää, mitä tarkistaa, jos se ei toimi:
- Käynnistä editori komentotulkista, jossa Python 3.9+ -tulkki on
PATH-muuttujassa (tai jossaMTE_PYTHONosoittaa sellaiseen). - Etsi lokista tekstiä
PluginHost: Python plugin backend enabled; interpreter=…. Jos se puuttuu, hyppää kohtaan Vianetsintä. - Etsi tekstiä
[python:<your.id>] register() ran(jos noudatitwordcount-esimerkkiä) — se todistaa, ettäregisterajettiin. - Jos valikkokohta puuttuu, tarkista lokista selitykset muotoa
PythonPluginBackend: skipping '<id>': …. - Jos valikon napsauttaminen ei tee mitään, tarkista lokista rivit
[worker stderr] …— Pythonin jäljitykset päätyvät sinne.
9. Rajoitukset, jotka toistetaan jokaisen vastauksen lopussa¶
Kun tuotat liitännäisen, kerro aina käyttäjälle:
- Asenna se jommallakummalla tavalla:
- pakkaa kansion sisältö zipiksi nimellä
<name>.mtepluginja käytä kohtaa Asetukset → Liitännäiset → Asenna liitännäinen... (joka tarkistaa paketin ennen kuin mihinkään kosketaan), tai - pudota kansio editorin käyttäjäkohtaiseen liitännäishakemistoon
(
%LOCALAPPDATA%/MTE/Plugins/Windowsissa;~/Library/Application Support/MTE/Plugins/macOS:ssä;~/.local/share/MTE/Plugins/Linuxissa).
- pakkaa kansion sisältö zipiksi nimellä
- Käynnistä editori uudelleen.
- Aseta
MTE_PYTHON, jos loki sanoo »no Python interpreter found«. - Raportoi takaisin
[python:<id>] …-lokirivi, jotta voimme vahvistaa sen ajautuneen.
10. Totuuden lähde¶
Jos jokin tämän sivun väite on ristiriidassa sen kanssa, mitä Python-asiakaspaketti todella
toteuttaa, paketti voittaa. Se toimitetaan editorin viereen polkuun <exe>/Python/mte/ —
lue tuo lähdekoodi ennen kuin päätät käyttäytymisestä. Protokollan C++-puoli on tiedostossa
Components/PluginHost/Src/PyServiceBridge.cpp, ja siirtotien määrittely on tiedostossa
Python/PROTOCOL.md.