Přeskočit obsah

ctx — kontext zásuvného modulu

register(ctx) dostává jeden argument: PluginContext. Zpřístupňuje níže uvedené služby (celou plochu IPluginContext, která se hodí k modelu mimo proces). Každá z nich je malá třída Pythonu, kterou pracovník staví nad kanálem RPC; zdroje jsou v Python/mte/.

def register(ctx):
    ctx.commands       # register/invoke commands
    ctx.menus          # add menu items & separators
    ctx.events         # subscribe to editor events
    ctx.log            # write to the host's diagnostics stream
    ctx.status         # post transient status-bar messages
    ctx.editor         # buffer / caret / selection / files / search / bookmarks / groups
    ctx.languages      # list languages, read / switch a document's language, contribute one
    ctx.settings       # per-plugin key/value store
    ctx.app_settings   # editor-wide read-only settings view (keys: mte.keys)
    ctx.document_settings  # .editorconfig-layered per-document reads
    ctx.theme          # themeable colour categories for your own UI
    ctx.diagnostics    # publish squiggles + margin glyphs
    ctx.compare        # open the side-by-side compare view
    ctx.completions    # register auto-completion providers
    ctx.docks          # declarative dock panels (+ set_panel_visible, is_panel_visible)
    ctx.line_markers   # margin glyphs / line tints that follow edits; the margin-click claim
    ctx.settings_page  # declarative Preferences pages
    ctx.ui             # runtime get/set for panel controls

Dvě části nativního kontraktu zatím nemají protějšek v Pythonu: poskytovatelé hoveru (prioritní pásma hoveru se proto na moduly v Pythonu nevztahují) a poskytovatelé místní nabídky editoru (proto ani oblast text / okraj místní nabídky). Obojí se zodpovídá, zatímco uživatel čeká, a to vyžaduje totéž ošetření termínem, jaké mají poskytovatelé dokončování, než bude cesta k pracovníkovi a zpět bezpečná.

Neukládejte ctx do globální proměnné s tím, že jej použijete později — atributy jsou sice po dobu života modulu stabilní, ale s ctx zacházejte jako s věcí jedné registrace. Potřebujete-li je, podržte si odkazy na jednotlivé služby (například log = ctx.log).


commands

ctx.commands.command(*, id, title, shortcut="", category="", scope="")

Dekorátor, který registruje příkaz a jako jeho obsluhu naváže obalený volatelný objekt.

  • id (str, povinné): jedinečný identifikátor ve stylu obráceného DNS (například "wordcount.count"). Stejná pravidla jako v plugin.json — musí být globálně jedinečný.
  • title (str, povinné): čitelný popisek zobrazovaný všude, kde se příkaz objeví (nabídky, paleta příkazů).
  • shortcut (str, volitelné): přenositelný výchozí řetězec zkratky, například "Ctrl+Alt+W". Ctrl se automaticky mapuje na hlavní modifikátor dané platformy (na macOS Cmd). Ponechte prázdné, nechcete-li žádnou. Uživatel ji může přiřadit znovu nebo její vazbu zrušit v Předvolby ▸ Klávesové zkratky; u příkazů vázaných na nabídku uplatní přemapování hostitel za vás.
  • category (str, volitelné): volný seskupovací popisek — zobrazuje se jako kategorie příkazu v Mapování klávesových zkratek.
  • scope (str, volitelné): ""/"application" (výchozí — hostitel zkratku naváže na akci nabídky daného příkazu) nebo "pluginWindow" pro kombinace bydlící v rozhraní vlastněném modulem: hostitel příkaz pouze vypíše v mapování; při stavbě svých widgetů si přečtěte ctx.commands.effective_shortcut(id) a při události "command.shortcutChanged" jej uplatněte znovu.
ctx.commands.effective_shortcut(command_id) -> str

Zkratka právě účinná po přemapování uživatelem a po vyřešení konfliktů ("" = bez vazby nebo neznámá). Volat ji z obsluh je bezpečné.

Obalená funkce nebere žádné argumenty a nic nevrací. Výjimky uvnitř ní pracovník zachytí a zaznamená na stderr; editor neshodí.

@ctx.commands.command(id="wordcount.count",
                      title="Count Words",
                      shortcut="Ctrl+Alt+W",
                      category="Text")
def _count():
    ctx.status.show("counted")

Pod kapotou: strana Pythonu si místně přidělí handlerId, zaregistruje zavolání na spojení RPC a u hostitele vyvolá commands.registerCommand. Když uživatel příkaz aktivuje (kliknutí v nabídce, zkratka, paleta), pošle hostitel RPC callback a vaše funkce se spustí.

Id příkazů platí v celém procesu, nikoli pro jednotlivý modul. Aby nedocházelo ke srážkám, opatřete svá id předponou svého jmenného prostoru v obráceném DNS (wordcount.…).


Cesty v nabídkách používají jako oddělovač /. Nejlevější segment je nabídka nejvyšší úrovně (Plugins, Tools, …); mezilehlé segmenty vytvářejí podnabídky na vyžádání; poslední segment je popisek položky.

ctx.menus.add_item(path, command_id)

ctx.menus.add_item("Plugins/Word Count/Count", "wordcount.count")
  • path (str): "Nejvyšší/Pod/…/Popisek". Mezilehlé podnabídky se vytvoří, pokud neexistují.
  • command_id (str): id dříve zaregistrovaného příkazu. Kliknutí na položku příkaz zavolá.

Vrací neprůhledný řetězec handlerId. Podržte si jej, budete-li později potřebovat položku odhlásit (ve fázi 1 vzácné).

Nejlevější segment (výše "Plugins") musí odpovídat title některé z vašich položek menus[] v plugin.json, aby se umístění v nabídkové liště převzalo.

ctx.menus.add_separator(path)

ctx.menus.add_separator("Plugins/Word Count")

Vloží oddělovač do existující podnabídky.

ctx.menus.set_item_checked(handler_id, checked)

Promění dříve přidanou položku v zaškrtávatelnou položku nabídky a nastaví její zaškrtnutý stav — standardní idiom pro trvalý přepínač zapnuto/vypnuto s viditelnou značkou.

item = ctx.menus.add_item("View/Auto-open Preview",
                          "myplugin.toggleAutoOpen")

# Seed the check mark from the persisted setting so it matches
# reality before the user opens the menu.
ctx.menus.set_item_checked(item, ctx.settings.get("autoOpen", True))

@ctx.commands.command(id="myplugin.toggleAutoOpen",
                      title="Toggle Auto-Open")
def _toggle():
    on = not ctx.settings.get("autoOpen", True)
    ctx.settings.set("autoOpen", on)
    ctx.menus.set_item_checked(item, on)

První volání položku povýší na zaškrtávatelnou; další volání už jen přehazují značku. Předání handler_id, které hostitel nezná, je tichou prázdnou operací (bezpečné volat i poté, co jste svůj token zahodili, například ze zpožděného zavolání spouštějícího se během ukončování).

ctx.menus.set_item_enabled(handler_id, enabled)

Zapíná nebo zašedne dříve přidanou položku. Použijte to, když příkaz potřebuje splněnou podmínku — něco k zavření, něco k obnovení — aby nabídka ukázala, že je nedostupný, místo aby nabízela položku, která tiše nic neudělá.

item = ctx.menus.add_item("File/Close Thing", "myplugin.closeThing")

# Nothing to close yet:
ctx.menus.set_item_enabled(item, False)

# ... and from every place that changes the state the command needs:
ctx.menus.set_item_enabled(item, thing_is_open)

Vypnutá položka zůstává viditelná, ale nelze ji spustit, ani kliknutím, ani zkratkou. Volání je idempotentní, takže si nikdy nemusíte pamatovat předchozí stav, a neznámé handler_id je tichou prázdnou operací jako výše. Položky začínají zapnuté: jedno volání hned po add_item nastaví položku, která při spuštění není dostupná.


events

Odebírejte události editoru. Dvě rovnocenná rozhraní — doporučené jsou sladké dekorátory; surové subscribe() je k dispozici, chcete-li téma spočítat za běhu.

Sladké dekorátory

@ctx.events.on_document_saved
def _saved(ev):
    ctx.log.info(f"saved doc {ev.document}")

@ctx.events.on_document_opened
def _opened(ev):
    ctx.log.info(f"opened {ev.path}")

Obalená funkce bere jeden argument: typovanou dataclass odpovídající danému tématu. Úplnou tabulku téma ↔ dataclass najdete v referenci událostí.

Surové rozhraní

handler_id = ctx.events.subscribe("document.saved", _saved)
# later:
ctx.events.unsubscribe(handler_id)

Dostupné sladké vlastnosti (samé dekorátory):

Vlastnost Spouští se při
on_document_opened Otevření dokumentu v kartě
on_document_closed Zavření dokumentu
on_document_saved Zápisu dokumentu na disk
on_selection_changed Pohybu kurzoru či výběru v aktivním dokumentu
on_active_document_changed Změně karty v popředí
on_document_modified_changed Přehození příznaku čistý/změněný (modified: bool)
on_text_changed Každé úpravě (position, added_bytes, removed_bytes) — držte ji triviální
on_settings_changed Uplatnění Předvoleb (scope: "" celý editor, "plugins/<id>")
on_theme_changed Přepnutí či úpravě motivu (variant) — přečtěte znovu barvy z ctx.theme
on_workspace_root_changed Otevření či zavření složky pracovního prostoru (root)
on_workspace_files_changed Změně sady souborů pracovního prostoru (files)
on_host_window_shown Jednou za běh, po prvním snímku — naplánujte sem pomalou práci při startu
on_app_about_to_quit Zavírání editoru — poslední příležitost něco udělat
on_command_shortcut_changed Změně účinné zkratky příkazu (přemapování uživatelem / import mapy kláves / řešení konfliktu); data: command_id, sequence ("" = bez vazby)

Neznámé názvy témat vyhodí při odběru ValueError. Odběratelé mohou běžet až po odpovídající nativní obsluze; doručení události berte jako informativní, nikoli závazné.

Publikování

ctx.events.publish(topic, payload) rozhlašuje nativním i pythonovským odběratelům — přijímá se pouze pro dvě témata o pracovním prostoru (workspace.rootChanged, workspace.filesChanged); cokoli jiného vyhodí ValueError. Viz reference událostí.


log

ctx.log.trace  ("very-fine-grained")
ctx.log.debug  ("dev-only detail")
ctx.log.info   ("routine event")
ctx.log.warning("recoverable weirdness")
ctx.log.error  ("something is wrong")

Každá úroveň se mapuje na ILogger hostitele. Řádky mají předponu [python:<plugin.id>], takže je váš výstup v diagnostickém protokolu editoru snadno k nalezení:

[python:org.example.wordcount] counted 42 words

Ze zásuvného modulu nikdy nevolejte print() — stdout pracovníka je kanálem RPC a zápis do něj protokol poškozuje. ctx.log.* je jediným posvěceným způsobem, jak vypsat diagnostický text.


status

ctx.status.show(message, timeout_ms=0, level="info")

Pošle do stavového řádku pomíjivé oznámení.

  • message (str): co zobrazit. Jen krátké řetězce — stavový řádek má pevnou šířku.
  • timeout_ms (int, výchozí 0): jak dlouho je držet na obrazovce; 0 nechá hostitele zvolit rozumnou výchozí hodnotu (několik vteřin).
  • level (str, výchozí "info"): "info" / "warning" / "error". Vyšší úrovně mohou minimum na obrazovce podržet o něco déle, aby chybu okamžitě nesmazala rutinní informace.
ctx.status.show("saved", timeout_ms=1500)
ctx.status.show("no results", level="warning")

ctx.status.show funguje ve stylu spusť a zapomeň. Nemá-li hostitel viditelný stavový řádek (bezhlavé či minimální sestavení), volání se tiše zahodí. ctx.status.clear() smaže jakékoli pomíjivé oznámení na obrazovce.

Trvalé části

Pro živý stav (počet slov, stav serveru) použijte část, kterou vlastníte a obnovujete — nikdy nepřebije oznámení a naopak:

seg = ctx.status.add_segment("wordcount.total", priority=100)
seg.set_text("2,431 words")
seg.set_visible(False)      # hide without giving it up
seg.remove()                # take it off the bar (idempotent)

priority řadí části modulů zleva doprava (nižší = dál vlevo; při shodě zůstává pořadí registrace). Ukončení modulu odebere vše, co ještě drží.


editor

Plný synchronní přístup k aktivním dokumentům. Každá metoda blokuje volající vlákno, dokud hostitel neodpoví (obvykle mikrosekundy, když pracovník i editor sdílejí stroj). Legální z libovolné obsluhy příkazu či události; nikdy ze zavolání async def (viz poznámka o vláknech na začátku této stránky).

Vyjmenování dokumentů

doc  = ctx.editor.active_document()      # 0 when no document is open
docs = ctx.editor.open_documents()       # list[int]
ctx.editor.set_active_document(doc)
path = ctx.editor.file_path(doc)         # str or None for untitled

Přístup k bufferu

text  = ctx.editor.text(doc)                        # whole buffer
slice = ctx.editor.text_range(doc, start, end)      # [start, end) bytes
n     = ctx.editor.length(doc)                      # int64
ctx.editor.set_text(doc, "...")

text_range zabrání tažení celého bufferu přes RPC, když potřebujete jen úsek — u řazení a transformací velkých souborů mu dávejte přednost.

Výběr

sel = ctx.editor.selection_range(doc)               # mte.MatchRange(start, end)
s   = ctx.editor.selected_text(doc)
ctx.editor.replace_selection(doc, "new text")
ctx.editor.set_selection(doc, mte.MatchRange(4, 9))

ranges = ctx.editor.selection_ranges(doc)           # list[MatchRange]: one per caret
block  = ctx.editor.is_rectangular_selection(doc)   # Alt+drag column block?

Kurzor

pos = ctx.editor.caret(doc)                         # mte.EditorPosition(line, column)
ctx.editor.set_caret(doc, mte.EditorPosition(line=7, column=3))

Operace se soubory

new_doc = ctx.editor.open_file("/path/to/file")     # doc-id or 0 on failure
here    = ctx.editor.open_file_at("/path/to/file", mte.EditorPosition(41, 0))
ctx.editor.reveal_line(doc, 120)                    # scroll there; caret and focus stay
ok      = ctx.editor.save_document(doc)
closed  = ctx.editor.close_document(doc)            # prompts if modified; False = user cancelled
dirty   = ctx.editor.is_modified(doc)
ctx.editor.set_modified(doc, True)                  # force the tab asterisk
ro      = ctx.editor.is_read_only(doc)              # e.g. Monitoring (tail -f) mode
enc     = ctx.editor.document_encoding(doc)         # mte.DocumentEncoding, e.g. UTF8_BOM
ctx.editor.set_document_encoding(doc, mte.DocumentEncoding.UTF8)  # takes effect on the next save
eol     = ctx.editor.line_ending(doc)               # mte.LineEnding, e.g. CRLF
ctx.editor.set_line_ending(doc, mte.LineEnding.LF)  # Enter inserts it; the next save writes it
scratch = ctx.editor.create_untitled("text", "Untitled-3")   # new buffer, doc-id
sid     = ctx.editor.stable_id(doc)                 # survives Save As; compare with ==
clip    = ctx.editor.clipboard_text()               # system clipboard, "" if no text

# Declare that you snapshot unsaved buffers (the host then stops asking
# "save changes?" when the window closes):
ctx.editor.set_unsaved_changes_preserved(True)
ctx.editor.unsaved_changes_preserved()              # bool, global flag

open_file_at je jediné volání pro »přenes uživatele tam« (chyba sestavení, rámec zásobníku, výsledek hledání): otevře soubor nebo aktivuje jeho kartu, umístí kurzor bez výběru, rozbalí sbalení, které řádek skrývá, posune řádek do zobrazení — na střed, pokud byl mimo obrazovku — a předá fokus editoru. Řádek a sloupec se počítají od 0 (sloupec v bajtech UTF-8) a ořezávají se, nikdy se neodmítají; vrací id dokumentu, nebo 0, když soubor nešlo otevřít, a pak se nic nepohnulo. reveal_line jen ukáže řádek: nepřesune kurzor, nepřepne kartu a nevezme fokus.

Záložky, označení, zvýrazňování URL

ctx.editor.toggle_bookmark(doc, line)               # 0-based line
lines = ctx.editor.bookmarked_lines(doc)            # list[int]
nxt   = ctx.editor.next_bookmark(doc, from_line, wrap=True)      # -1 = none
prv   = ctx.editor.previous_bookmark(doc, from_line, wrap=True)
ctx.editor.clear_bookmarks(doc)

ctx.editor.mark_range(doc, mte.MatchRange(4, 9))    # "mark" indicator
ctx.editor.clear_marks(doc)

ctx.editor.set_url_highlight_enabled(doc, True)
ctx.editor.is_url_highlight_enabled(doc)
url = ctx.editor.url_at_caret(doc)                  # "" when none
ctx.editor.open_url_at_caret(doc)                   # bool

Hledání

opts = mte.SearchOptions(match_case=True, mode="regex")
hit  = ctx.editor.find_in_range(doc, r"TODO\(.*\)", 0,
                                ctx.editor.length(doc), opts)
if hit.valid:
    new_end = ctx.editor.replace_target(doc, hit, "DONE()", opts)

SearchOptions.mode: "normal" / "extended" / "regex". SearchOptions.direction: "forward" / "backward". Úplný seznam polí najdete u mte.SearchOptions.

Přírůstkové hledání (vzor roste, jak uživatel píše, od připnutého ukotvení):

ctx.editor.set_incremental_search_anchor(doc)
hit = ctx.editor.incremental_search(doc, "TOD", opts)   # MatchRange

Odsazení a syntaxe

style = ctx.editor.indentation(doc)                 # IndentationStyle(use_tabs, width)
ctx.editor.set_indentation(doc,
    mte.IndentationStyle(use_tabs=False, width=2))

# Where word-wrapped continuation rows start:
# "left" (window edge) / "same" (align with the line; default) / "indent"
ctx.editor.set_wrap_indent(doc, "same")
mode = ctx.editor.wrap_indent(doc)                  # same strings back

lang  = ctx.editor.colorizer_id(doc)                # e.g. "python", "" if none

# Semantic classification from the active colorizer (cheap, no re-lex):
# list[mte.CategorySpan(start, length, category)] with category names
# "comment" / "string" / "keyword" / ... -- a spell checker keeps to
# comments and strings this way.
spans = ctx.editor.text_categories(doc, 0, ctx.editor.length(doc))

ctx.editor.trigger_completion(doc)                  # pop the list as if Ctrl+Space

Rozdělené zobrazení a skupiny karet

n  = ctx.editor.group_count()                       # >= 1
g  = ctx.editor.group_of(doc)                       # -1 if not open
w  = ctx.editor.group_weights()                     # relative widths
ctx.editor.move_document_to_group(doc, 1)           # creates trailing groups on demand
ctx.editor.set_group_weights([2, 1])
ctx.editor.set_document_tab_index(doc, 0)           # reorder within its pane

gs = ctx.editor.groups_of(doc)                      # EVERY pane showing it
ctx.editor.show_document_in_group(doc, 1)           # split it into pane 1

Dokument zobrazený ve dvou panelech je jeden dokument s jedním id, takže group_of() může pojmenovat jen jedno z jeho míst. groups_of() hlásí všechna a show_document_in_group() vytvoří druhé sdílené zobrazení — totéž, co dělá Zobrazit > Rozdělit dokument vpravo, takže se nevyvolá žádná událost document_opened.

ctx.editor nyní zrcadlí nativní IEditorService jedna ku jedné.


languages

Obarvování syntaxe je funkcí jádra — hostitel rozpozná jazyk dokumentu a obarví jej bez jakéhokoli modulu. ctx.languages vám umožňuje se přidat: vypsat, co hostitel zná, přečíst nebo přebít jazyk dokumentu, znovu spustit rozpoznání a přispět celými definicemi jazyků.

for lang in ctx.languages.available():              # list[mte.LanguageInfo(id, name)]
    ...
lang_id = ctx.languages.language_for(doc)           # "" when uncoloured
ok      = ctx.languages.set_language_for(doc, "json")   # "" clears; False = unknown
ctx.languages.auto_detect_for(doc)                  # undo a manual override

handler = ctx.languages.register(
    id="mylang", name="My Language", lexer="cpp",
    extensions=["mylang", "ml2"],
    keywords=[{"group": "Primary keywords and identifiers",
               "words": "if else while"}],
    styles={"SCE_C_COMMENT": "comment", "SCE_C_STRING": "string"})
...
ctx.languages.unregister(handler)                   # restores what it displaced

register bere týž tvar jako dodávaný soubor colorizers/*.json (nebo celý slovník definition=): lexer pojmenovává existující lexer backendu (hostitel za běhu nové lexery nezískává), styles mapuje názvy stylů daného lexeru na významové kategorie a keywords adresuje jeho seznamy slov podle vlastního popisu slotů lexeru — hostitel obojí vyhodnotí přesně tak, jak to dělá u dodávaného souboru, takže nikdy nepíšete id enginu. Registrace id, které už existuje, jej nahradí na tak dlouho, dokud držíte id popisovače; ukončení modulu stáhne vše, co ještě drží.


document_settings

Nastavení dokumentu jen ke čtení, která kladou projektový .editorconfig nad vaše vlastní ctx.settings. Pro klíč k a dokument doc čtení vyhodnotí mte.<plugin.id>.<k> z .editorconfig platného pro onen dokument, pak se vrátí k ctx.settings a pak k vaší výchozí hodnotě:

wrap  = ctx.document_settings.get_bool(doc, "wrap", False)
width = ctx.document_settings.get_int(doc, "width", 80)
ratio = ctx.document_settings.get_double(doc, "ratio", 1.0)
name  = ctx.document_settings.get_string(doc, "profile", "")
ctx.document_settings.contains(doc, "wrap")          # set in either layer?

Zápisy jdou přes ctx.settings — nikdy zpět do .editorconfig.


diagnostics

Vysílejte pro dokument vlnovky a značky na okraji — z linteru, z jazykového serveru, z protokolu sestavení. Hostitel je vykresluje; zobrazení se nikdy nedotknete. Publikování nahradí vše, co jste pro danou dvojici (dokument, zdroj) publikovali dřív, proto pokaždé posílejte úplnou sadu:

ctx.diagnostics.publish(doc, "mylinter", [
    mte.Diagnostic(start=12, end=18, message="unused variable",
                   severity=mte.SEVERITY_WARNING, code="W0612"),
])
ctx.diagnostics.publish(doc, "mylinter", [])        # retract only your own
ctx.diagnostics.clear(doc)                          # clears EVERY producer's set on doc

source je povinný — právě on drží vaši sadu oddělenou od sad jiných producentů (modulu LSP, kontroly pravopisu) na témž dokumentu. Závažnosti: SEVERITY_ERROR / SEVERITY_WARNING / SEVERITY_INFORMATION / SEVERITY_HINT. Posuny jsou polootevřené bajtové rozsahy [start, end).


theme

Zaregistrujte barvy, které vaše vlastní rozhraní vykresluje, jako pojmenované kategorie z motivu; Editor motivů je automaticky vypíše a přebití od uživatele se obousměrně přenášejí v souboru motivu:

h = ctx.theme.register_category("com.example.my/match", "Match highlight",
                                default_light=0xFFF3B0, default_dark=0x8A6D00)
rgb     = ctx.theme.resolve_color("com.example.my/match")   # 0xRRGGBB int
themed  = ctx.theme.has_override("com.example.my/match")
variant = ctx.theme.active_variant()                        # "light" / "dark" / ""
cats    = ctx.theme.categories()                            # list[mte.ThemeCategory]
ctx.theme.unregister_category(h)

Při ctx.events.on_theme_changed si resolve_color přečtěte znovu.


compare

Otevře (nebo zaměří) zobrazení porovnání vedle sebe nad dvěma zdroji. Každá strana je soubor na disku (path=), doslovný text (content=), nebo obojí (zobrazí se obsah, cesta dá uchovávání relace obnovitelnou identitu):

shown = ctx.compare.open(
    mte.CompareSource("before", path="/tmp/a.txt"),
    mte.CompareSource("after", content=new_text),
    ignore_whitespace=True)                          # ignore_case / ignore_eol too

Vrací False, když některou stranu nelze přečíst.


settings

Trvalé typované úložiště klíč/hodnota, na hostiteli omezené na plugins/<your.id>/. Všechny metody jsou synchronní; get a set se odvozují z typu výchozí hodnoty v Pythonu.

ctx.settings.set("wrap", True)
ctx.settings.set("count", 42)
ctx.settings.set("theme", "dark")

wrap  = ctx.settings.get("wrap", False)             # returns bool
count = ctx.settings.get("count", 0)                # returns int
theme = ctx.settings.get("theme", "light")          # returns str

Typy:

Typ default v Pythonu Typ na přenosu Návratový typ z get()
bool bool bool
int (mimo bool) int int
float double float
str (nebo None) string str

Dále:

ctx.settings.contains("wrap")           # bool
ctx.settings.remove("stale_key")
ctx.settings.sync()                     # optional; host also syncs on shutdown

Na změny provedené v okně Předvolby reagujte odběrem settings.changed (ctx.events.on_settings_changed; viz reference událostí).

app_settings

Pohled na konfiguraci celého editoru jen ke čtení. Známé názvy klíčů se exportují jako konstanty v mte.keys (zrcadlí hlavičku C++ PluginApi/AppSettingsKeys.h, která zůstává závazným seznamem):

import mte
size   = ctx.app_settings.get(mte.keys.EDITOR_FONT_SIZE, mte.keys.DEFAULT_EDITOR_FONT_SIZE)
family = ctx.app_settings.get(mte.keys.EDITOR_FONT_FAMILY, "")   # "" = host default
theme  = ctx.app_settings.get(mte.keys.UI_THEME, mte.keys.DEFAULT_UI_THEME)  # "system"/"light"/"dark"

Settery, remove() i sync() na ctx.app_settings vyhazují RuntimeError — pohled je záměrně jen ke čtení.


completions

Zaregistrujte poskytovatele dokončování, který se účastní nabídky v editoru:

@ctx.completions.provider(languages=["python"])
def _complete(req: mte.CompletionRequest):
    if not req.prefix:
        return []
    return [mte.CompletionItem(text=w, kind=mte.KIND_KEYWORD)
            for w in ("hello", "hello_world", "helper")
            if w.startswith(req.prefix)]

Obalený volatelný objekt se vyvolá při každém stisku klávesy (nebo při Ctrl+Space), jehož dokument odpovídá jazykovému filtru. Pole na req:

Pole Typ Význam
document int Upravovaný dokument
prefix str Fragment slova bezprostředně před kurzorem
position int Bajtový posun kurzoru
language_id str Id obarvovače ("python", "cpp", …)
manual bool True, když uživatel stiskl Ctrl+Space; False u automatického vyvolání

Vraťte seznam mte.CompletionItem (nebo prosté řetězce, nebo slovníky s {text, detail, kind}). Druhy: mte.KIND_KEYWORD / KIND_WORD / KIND_SYMBOL / KIND_SNIPPET / KIND_UNKNOWN.

Termín

Hostitel posílá vašemu poskytovateli požadavek na stisk klávesy s termínem asi 30 ms. Nestihne-li jej vaše zavolání, nabídka se pro daný stisk zobrazí bez vašich kandidátů. Jakýkoli drahý index budujte na pozadí a odpovídejte z mezipaměti — v zavolání nikdy neprovádějte vstupně-výstupní operace. Síťové dokončování ve stylu LSP patří do budoucí asynchronní plochy, nikoli sem.

languages=[] (nebo vynechání) znamená »každý dokument«. Několik poskytovatelů na modul je povoleno; každý dekorátor registruje svého vlastního.


data_dir

path = ctx.data_dir()                    # pathlib.Path

Vrací zapisovatelný adresář daného modulu, který pro vás hostitel už vytvořil. Používejte jej pro mezipaměti, indexy a jakákoli data, která vlastníte. ctx.diagnostics_dir() obdobně vrací sdílený adresář s pády a protokoly (pro vás jen ke čtení — zapisuje tam hlášení o pádech).


docks

Deklarativní ukotvitelné panely — rozhraní postranního panelu postavené ze specifikace formuláře, nikoli z widgetů Qt. Slovník stavitelů najdete u mte.ui.

from mte.ui import Column, Row, Label, LineEdit, Checkbox, Button

def register(ctx):
    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="renamer.panel",
        title="Renamer",
        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="renamer.run")]),
        ]),
    )

ctx.docks.add_panel(*, id=None, title="", area="right", form, initially_visible=True, proportional_size=0.0, tab_group="") -> str

Registruje ukotvitelný panel. Vrací id panelu (přidělené automaticky, je-li id rovno None).

  • id (str, volitelné) — stabilní identifikátor, kterým ctx.ui.get / ctx.ui.set adresují ovládací prvky uvnitř tohoto panelu. Pokud žádné nedodáte, hostitel přidělí id py-dock/N a vrátí je.
  • title (str) — zobrazuje se v hlavičce doku.
  • area (str) — "left" / "right" / "top" / "bottom". Neznámé hodnoty se vrátí k "right".
  • form (dict) — specifikace postavená staviteli mte.ui.
  • initially_visible (bool) — zda se dok zobrazí, když se modul načte. Výchozí True. Předejte False u panelu, který by při spuštění vyskakovat neměl, a odkryjte jej později pomocí set_panel_visible.
  • proportional_size (float) — výchozí velikost panelu jako podíl hlavního okna: jeho šířky v oblastech "left" / "right", jeho výšky v "top" / "bottom". 0.0 (výchozí hodnota) zachovává pixelové chování, které chce nástrojový panel — panel si drží svou velikost a změna velikosti okna se ho nedotkne. Zlomek jako 0.4 je to, co chce náhled: maximalizace okna panel rozšíří spolu s ním. Vaše je jen výchozí hodnota; jakmile uživatel přetáhne oddělovač panelu, hostitel jeho podíl přijme a zapamatuje si jej. Hodnoty mimo rozsah se omezí a hostitel zachová minimální rozměr pro panel i editor. Úplná pravidla najdete u proportionalSize.
  • tab_group (str) — panely se stejným neprázdným id skupiny ve stejné area sdílejí jeden dok jako karty, místo aby ležely vedle sebe. Dejte mu jmenný prostor jako id panelu ("renamer/panels"). Jen výchozí uspořádání: rozvržení uložené uživatelem má přednost. Viz tabGroup.

ctx.docks.is_panel_visible(handler_id) -> bool

Zda je panel přidaný přes add_panel otevřený — panel schovaný jako karta za jiným se počítá jako otevřený; panel zaregistrovaný s initially_visible=False a nikdy nezobrazený, zavřený panel a neznámé id ne. Kombinujte ho se set_panel_visible, chcete-li zobrazit své panely po dobu úlohy a potom vrátit, co měl uživatel:

was_open = ctx.docks.is_panel_visible(panel)
ctx.docks.set_panel_visible(panel, True)
# ... when the task ends:
ctx.docks.set_panel_visible(panel, was_open)

ctx.docks.set_panel_visible(handler_id, visible)

Odkrývá nebo skrývá panel přidaný pomocí add_panel. Zobrazení panel zároveň zvedne nad doky, s nimiž sdílí oblast, takže příkaz, který svůj panel »otevírá«, jej uživateli skutečně postaví před oči. Idempotentní — tutéž hodnotu opakujte, jak chcete, není co sledovat; u neznámého id se tiše ignoruje.

panel = ctx.docks.add_panel(id="mine.panel", title="Mine", form=form,
                            initially_visible=False)

@ctx.commands.command("mine.show", title="Show My Panel")
def _show():
    ctx.docks.set_panel_visible(panel, True)

ctx.docks.on_change(callback) -> str

Přidělí id obsluhy pro propojení pole on_change ovládacího prvku s volatelným objektem v Pythonu. Zavolání dostane novou hodnotu jako řetězec; logické prvky doručují "true" / "false", číselné prvky doručují celé číslo jako řetězec.

def _on_volume(value: str) -> None:
    ctx.log.info(f"volume={int(value)}")

handler = ctx.docks.on_change(_on_volume)
form = Column([NumberSpin(id="volume", default=5, on_change=handler)])

Id obsluh on_change mají předponu py-dock-cb/N a nikdy se nesrazí s id obsluh příkazů, událostí ani dokončování.

Zavolání se provádí ve smyčce událostí pracovníka, NIKOLI ve fondu vláken — držte je rychlé. Používejte ctx.log, měňte malý stav v paměti, nebo spusťte navazující příkaz; těžkou práci tu nedělejte.


line_markers

Symboly na okraji a podbarvení celých řádků připojené k řádkům dokumentu, které sledují úpravy uživatele — zarážky, šipka aktuálního příkazu, pruhy změn — a nárok na kliknutí do sloupce záložek. Nativní službu a všechna její pravidla popisuje stránka Značky řádků a kliknutí na okraj.

bp_colour = ctx.theme.register_category("myplugin.breakpoint", "Breakpoint",
                                        default_light=0xCC0000,
                                        default_dark=0xFF5555)
bp = ctx.line_markers.register_kind(glyph=mte.GLYPH_CIRCLE,
                                    glyph_color="myplugin.breakpoint",
                                    line_tint="", z_order=50)

marker = ctx.line_markers.add_marker(bp, doc, line)   # 0 = refused
ctx.line_markers.line_of(marker)                      # where it is NOW; -1 = gone
ctx.line_markers.markers(bp, doc)                     # [mte.LineMarkerPlacement(id, line)]
ctx.line_markers.remove_marker(marker)
ctx.line_markers.clear_markers(bp, doc)               # doc 0 = every document
ctx.line_markers.release(bp)                          # removes the kind and its markers
  • register_kind(glyph=, glyph_color=, line_tint=, z_order=) — glyph je jedna z hodnot mte.GLYPH_NONE (jen podbarvení), GLYPH_CIRCLE, GLYPH_CIRCLE_HOLLOW, GLYPH_CIRCLE_BAR, GLYPH_ARROW, GLYPH_STRIP; barvy jsou id kategorií, které jste zaregistrovali přes ctx.theme. Editor má pro všechny moduly dohromady jen několik slotů pro značky, takže volání může selhat — pak vyvolá RpcError. Své druhy registrujte jednou, v register().
  • add_marker(kind, doc, line) — řádek počítaný od 0, neořezávaný: řádek mimo dokument vrátí 0 a nic neumístí.
  • Značky se posouvají se svými řádky, přežijí znovunačtení na stejném čísle řádku a mizí se svým dokumentem — své značky přidávejte znovu v ctx.events.on_document_opened.

Kliknutí na okraj

@ctx.events.on_margin_clicked
def _clicked(ev):                       # mte.MarginClicked
    if ev.modifiers & mte.MODIFIER_CONTROL:
        edit_condition(ev.document, ev.line)
    else:
        toggle_breakpoint(ev.document, ev.line)

claim = ctx.line_markers.claim_margin_click()

Každé kliknutí levým tlačítkem do sloupce záložek se publikuje jako margin.clicked (document, line, margin = "symbol", modifiers — bity mte.MODIFIER_SHIFT / MODIFIER_CONTROL / MODIFIER_ALT / MODIFIER_META). Ve výchozím stavu editor při něm také přepne záložku; dokud některý modul drží claim_margin_click(), nedělá to a kliknutí přenechá modulům. ctx.line_markers.release(claim) kliknutí vrátí.


settings_page

Přidává stránku do okna Předvolby editoru. Týž slovník specifikace formuláře jako u ukotvitelných panelů; totéž uchovávání pomocí binds="settings:<key>".

from mte.ui import Column, Checkbox, NumberSpin

def register(ctx):
    ctx.settings_page.register(
        category="Plugins/Renamer",       # slash-separated tree path
        title="Renamer",
        form=Column([
            Checkbox(id="cs", label="Case sensitive by default",
                     binds="settings:caseSensitive"),
            NumberSpin(id="results", default=100, min=1, max=10_000,
                       binds="settings:resultCap"),
        ]),
    )

ctx.settings_page.register(*, category, title, form, scope="") -> str

  • category (str, povinné) — cesta ve stromu Předvoleb oddělená lomítky, například "Plugins/My Plugin". Vnořené kategorie přicházejí jako uzly stromu.
  • title (str, povinné) — nadpis zobrazený nad stránkou.
  • form (dict, povinné) — specifikace postavená staviteli mte.ui.
  • scope (str, volitelné) — rozsah události vydávaný v SettingsChanged poté, co uživatel stiskne OK / Použít.

Vrací id obsluhy.

Zrušit změny NEVRACÍ

Ovládací prvky s binds="settings:<key>" se ukládají dychtivě, jak uživatel upravuje, takže stisk Zrušit v okně Předvolby je nevrátí. Je to známé omezení fáze 3; budoucí upřesnění podkladová nastavení obalí do proxy s vyrovnávací pamětí.


ui

Čte a zapisuje hodnoty ovládacích prvků uvnitř panelů, které jste už zaregistrovali. Hodnoty jsou na přenosu vždy řetězce.

def register(ctx):
    panel_id = ctx.docks.add_panel(id="renamer.panel", ...)

    @ctx.commands.command(id="renamer.echo", title="Echo needle")
    def _echo():
        needle = ctx.ui.get(panel_id, "needle") or ""
        cs = (ctx.ui.get(panel_id, "cs") or "false") == "true"
        ctx.status.show(f"needle={needle!r} case={cs}")

    @ctx.commands.command(id="renamer.clear", title="Clear needle")
    def _clear():
        ctx.ui.set(panel_id, "needle", "")

ctx.ui.get(panel_id, control_id) -> Optional[str]

Přečte aktuální hodnotu ovládacího prvku. Vrací None, když je panel nebo prvek neznámý (není to chyba — None i "" berte jako »prázdné«).

  • Label / Button / LineEdit / ComboBox — vrací text.
  • Checkbox — vrací "true" nebo "false".
  • NumberSpin — vrací celé číslo jako desítkový řetězec.
  • List — vrací text právě vybrané položky (prázdný, když není vybráno nic).

ctx.ui.set(panel_id, control_id, value) -> bool

Zapíše hodnotu do ovládacího prvku programově. Vrací True, když zápis uspěl, a False, když je panel či prvek neznámý nebo hodnotu nelze převést (například necelé číslo poslané do NumberSpin).

Pythonovské True / False se před odesláním převádějí na "true" / "false"; vše ostatní prochází přes str().

set řízený hostitelem znovu NESPOUŠTÍ zavolání on_change daného prvku, takže můžete své widgety aktualizovat z obsluhy příkazu bez nekonečných smyček.


Shrnutí vazeb za běhu (fáze 3)

Pole specifikace formuláře Chování za běhu
id="…" Adresuje ovládací prvek z ctx.ui.get / ctx.ui.set.
binds="settings:<key>" Načte počáteční hodnotu z ctx.settings; při každé úpravě uživatelem zapíše zpět.
on_click="<cmd>" Tlačítka při kliknutí vyvolají zaregistrované id příkazu.
on_change=handler Při úpravě uživatelem spustí zavolání zaregistrované přes ctx.docks.on_change(...).

Tatáž pravidla platí u ukotvitelných panelů i u stránek Předvoleb.