jot supports plugins written in Python and now has a more complete extension runtime: plugin packages, user commands with arguments, reload support, and a generic event system.
jot_api exposes a first-class namespace named jot.
vim still exists as a backward-compatibility alias for older configs.
Primary config is loaded from ~/.config/jot/configs/init.py.
Plugin files are loaded from ~/.config/jot/configs/plugins/.
Plugin directories are also supported. A plugin directory can contain either
plugin.py or __init__.py.
Legacy files in ~/.config/jot/*.py and ~/.config/jot/plugins/ still load.
~/.config/jot/
├── configs/
│ ├── init.py
│ ├── colors/
│ │ └── my_theme.py
│ └── plugins/
│ └── my_plugin.py
│ └── git_tools/
│ └── plugin.py
├── plugins/ # Legacy plugin dir
└── themes/ # Legacy theme dir
Create a .py file in ~/.config/jot/configs/plugins/ or wire it from init.py.
Single-file plugin:
from jot_api import jot
def my_command(_arg=""):
jot.notify("Hello from Plugin!")
jot.keymap.set("all", "ctrl+h", my_command)
jot.api.nvim_create_user_command("HelloPlugin", my_command, {})Package-style plugin:
# ~/.config/jot/configs/plugins/git_tools/plugin.py
from jot_api import autocmd, create_user_command, current_file, jot
@autocmd("startup")
def on_start(_event):
jot.notify("git_tools ready")
def blame_current_file(_arg=""):
jot.notify(f"Blame: {current_file()}")
create_user_command("GitBlameFile", blame_current_file)Legacy compatibility methods (generally not needed in normal modeless usage):
enter_normal_mode()enter_insert_mode()enter_visual_mode()move_cursor(dx, dy)insert_char(char)delete_char(forward_bool)save_file()open_file(path)show_message(text)execute_command(":w")orcommand(":w")
get_mode()-> str (compatibility value)get_cursor_x()-> intget_cursor_y()-> intget_current_file()-> strcurrent_file()-> strget_buffer_content()-> strset_buffer_content(text)get_selected_text()-> str
set_theme_color(name, fg, bg): Set color for a UI element.name: "keyword", "string", "comment", "background", etc.fg: Terminal color code (0-255).bg: Terminal color code (0-255). -1 for transparent/default.
jot.api.nvim_set_hl(0, group, spec): Jot highlight-group mapping via compatibility API.jot.cmd("colorscheme my_theme")orjot.cmd.colorscheme("my_theme")jot.notify(text, level="info")jot.keymap.set(mode, lhs, rhs)jot.api.nvim_create_user_command(name, callback, opts)jot.api.nvim_create_autocmd(event, opts)
Use autocmd(event, pattern="*") for event-driven plugins.
Built-in events:
startupbuffer_openbuffer_changebuffer_save
Example:
from jot_api import autocmd, jot
@autocmd("buffer_save", pattern="*.py")
def on_python_save(event):
jot.notify(f"saved {event['filepath']}")User commands receive a single string argument.
from jot_api import create_user_command, jot
def grep_word(arg=""):
jot.notify(f"search for: {arg}")
create_user_command("GrepWord", grep_word)Inside jot:
:GrepWord hello
Built-in plugin commands:
:PlugReload:PlugList:PlugHealth:PlugInfo [name-or-path-fragment]:PlugPolicy [off|warn|strict]:PlugAudit [limit] [query]:PlugAuditClear
Plugins can register lifecycle callbacks:
from jot_api import register_plugin_lifecycle, jot
def on_load():
jot.notify("plugin loaded")
def on_unload():
jot.notify("plugin unloading")
def on_reload():
jot.notify("plugin reloading")
register_plugin_lifecycle(
on_load=on_load,
on_unload=on_unload,
on_reload=on_reload,
)Plugins can also register metadata (name/version/author/dependencies):
from jot_api import register_plugin
register_plugin(
name="git_tools",
version="0.2.0",
author="you",
description="Extra git commands for jot",
depends=["lsp", "project"],
min_api="1.1.0",
min_python="3.10",
capabilities=["commands", "ex_commands", "write"],
)You can lazily activate plugin setup on events or commands:
from jot_api import register_plugin
def setup(reason=""):
# Heavy setup goes here (register keymaps/commands/etc.)
pass
register_plugin(
name="big_feature",
depends=["git_tools"],
setup=setup,
lazy_events=["buffer_open", "startup"],
lazy_commands=["bigfeature"],
# eager=True forces startup activation even if lazy_* is set
)Optional compatibility/safety gates:
min_api/max_api: gate byjot_apiruntime API version.min_jot/max_jot: gate by host editor version (JOT_VERSIONenv).min_python/max_python: gate by embedded Python version.requires_unsafe=True: activate only whenJOT_ALLOW_UNSAFE_PLUGINS=1.capabilities=[...]: declare privileged actions this plugin needs.
Current capability names:
commands- callexecute_command(...)ex_commands- callexecute_ex_command(...)write- callsave_file(...)workspace- callopen_workspace(...)terminal- calltoggle_terminal(...)sidebar- calltoggle_sidebar(...)quit- callrequest_quit(...)/save_and_quit(...)
Policy mode (JOT_PLUGIN_POLICY) defaults to off for backward compatibility.
Modes:
off: no capability enforcementwarn: allow but warn when undeclared capability is usedstrict: block undeclared capability usage Policy mode is persisted in~/.config/jot/configs/plugin_policy.jsonand restored on startup. Environment variableJOT_PLUGIN_POLICYoverrides the persisted value.
Audit:
- Capability checks in
warn/strictare recorded in an in-memory audit trail. - Use
:PlugAuditto inspect recent entries. - Use
:PlugAudit 50 lspto filter by query. - Use
:PlugAuditClearto reset the trail.
You can also inspect runtime plugin health:
from jot_api import plugin_health, plugin_health_summary, plugin_info
print(plugin_health_summary())
for item in plugin_health():
print(item["name"], item["loaded"], item["last_load_ms"], item["last_error"])
print(plugin_info("git_tools"))import jot_api
import threading
import time
def auto_save_loop():
while True:
time.sleep(30)
jot_api.save_file()
jot_api.show_message("Auto-saved!")
# threading.Thread(target=auto_save_loop).start()