Skip to content

Kasane 0.7.0

Latest

Choose a tag to compare

@Yus314 Yus314 released this 11 May 00:54
· 168 commits to master since this release
v0.7.0

[0.7.0] - 2026-05-11

The 0.7.0 release lands the R2.x P7+P9 cascade
(ADR-039)
removing WireFace from the public plugin API, the ABI 4.0.0 →
4.1.0 wire bump
for eval-command at session-ready (ADR-041) and
command-error-event plugin observability (ADR-042), and the
Issue #81 sprout-dogfooding suite (12 children + 1 follow-up
#97) delivering 3 layers of Kakoune-command construction tooling
(kak::*, kak_lint!, kak_cmd::KakCommand), a native plugin
test harness, and a host of SDK ergonomic helpers.

Migration: docs/migration/0.6-to-0.7.md
walks each breaking-change site plus the §§9.1–9.5 opt-in additions.

Added — kak::set_option + kak::set_option_add (closes #97)

Two SDK helpers that close a silent foot-gun surfaced by the second
wave of sprout dogfooding: Kakoune's %X{…} expansions (%opt{…},
%arg{N}, %val{…}, %sh{…}, %reg{c}) are not processed when
glued to a bareword
, only inside double-quoted strings or as
standalone tokens. So

set-option -add window ui_options sprout_request_arg=%arg{1}

stored the literal text sprout_request_arg=%arg{1} rather than the
expanded value — no Kakoune diagnostic, no compile error, just a
silently propagated literal that broke sprout's request dispatch.

The new helpers always wrap values in "…" so the expansion fires:

use kasane_plugin_sdk::kak::{self, Scope};

let cmd = kak::set_option_add(Scope::Window, "ui_options", &[
    ("sprout_request_id", "%opt{sprout_request_seq}"),
    ("sprout_request_kind", "pick"),
    ("sprout_request_arg", "%arg{1}"),
]);
// → set-option -add window 'ui_options'
//   "sprout_request_id=%opt{sprout_request_seq}"
//   "sprout_request_kind=pick"
//   "sprout_request_arg=%arg{1}"

Companion helper [kak::escape_arg_expand] doubles embedded " and
documents the bareword rule (linking
doc/pages/expansions.asciidoc); the docs are deliberately explicit
because the bug is invisible in normal testing. For literal values
where expansions should not happen, fall back to the existing
[kak::escape_arg] (single-quoted) and compose the command yourself.

examples/wasm/kakoune-bindings-demo gains a set_option_add call
demonstrating an expansion-bearing entry.

Added — Structured KakCommand enum (ADR-043)

Closes the last open child of the sprout-dogfooding tracker
(Issue #94). Adds the
kasane_plugin_sdk::kak_cmd module exposing a KakCommand enum that
represents a Kakoune command as a Rust value rather than a string:

use kasane_plugin_sdk::kak_cmd::{KakCommand, DeclareUserMode, DefineCommand, Map, Scope};

let setup: Vec<KakCommand> = vec![
    DeclareUserMode::new("sprout").try_idempotent().into(),
    DefineCommand::new("bump", "increment-counter")
        .override_existing()
        .docstring("bump the sprout counter")
        .into(),
    Map::new(Scope::Global, "sprout", "b", ":bump<ret>")
        .docstring("bump")
        .into(),
];

// Render each as a Kakoune command string.
let strings: Vec<String> = setup.iter().map(KakCommand::render).collect();

The renderer centralizes quoting/escaping (single-quote with ''
escape, balanced %X…Y body delimiters with {}[]()<>
fallback). Each variant has builder methods only for the flags Kakoune
accepts — DeclareUserMode exposes no override_existing() because
Kakoune does not accept that flag (the sprout regression that motivated
this whole tracker).

The new module complements the existing kak::* string builders and
kak_lint! validator; the cookbook in docs/plugin-development.md
documents the three-layer choice. Initial catalog covers 12 commands
(declare-user-mode, define-command, map, declare-option, set-option,
unset-option, evaluate-commands, hook, alias, echo, info, try);
adding a command is additive — one variant + one args struct.

Pure SDK addition: no WIT change, no ABI bump, no plugin recompile.
Opt-in via use kasane_plugin_sdk::kak_cmd;.

Added — Plugin DX long-term: test harness + command linter (Issue #81 long-term, 2026-05-11)

Closes two infrastructural items on the sprout dogfooding tracker —
#92 (plugin test harness) and #93 (static Kakoune-command linter).
The last child, #94 (structured KakCommand enum), is covered by the
ADR-043 entry above.

  • kasane-plugin-sdk-test crate (#92): mock-host harness for
    unit-testing plugins natively, without compiling to wasm32-wasip2
    or driving a real Kakoune. Provides TestHarness (setters mirroring
    the WIT host-state surface), MockHostState with structured
    Mock{Brush,Style,Atom,Coord,Info,Session} types,
    CommandRecord / drain_commands for Effects observation, and a
    MockElementArena recording every element_builder::* call.
    Re-exported as kasane_plugin_sdk::test under the SDK's
    test-harness feature, so plugin authors can opt in with a single
    Cargo.toml line:

    [features]
    test-harness = ["kasane-plugin-sdk/test-harness"]

    The macros emitted by define_plugin! / generate! now cfg-switch
    the host_state module: under test-harness on a non-wasm target
    they route into the mock; production WASM builds are unchanged.
    examples/wasm/cursor-line ships 4 end-to-end tests demonstrating
    the pattern. See docs/plugin-testing.md for the cookbook.

  • kak_lint! proc macro (#93): compile-time validator for raw
    Kakoune command strings. Catches the sprout-dogfooding bug —
    declare-user-mode -override (Kakoune silently rejects;
    declare-user-mode does not accept -override), which caused all 11
    user-mode keymaps to fail to register — before the plugin is ever
    loaded:

    // Compile error: unknown flag `-override` for `declare-user-mode`.
    let _ = kasane_plugin_sdk::kak_lint!("declare-user-mode -override 'sprout'");

    Hand-rolled tokenizer handles all four Kakoune quotation forms
    ('…' with '' escaping, %{…}/%[…]/%(…)/%<…>) and follows
    try into its body so the idiomatic
    try %[ declare-user-mode … ] wrapper still gets linted. The
    catalog ships with 12 setup-heavy commands; unknown commands pass
    through unchanged (additive policy — no false positives against
    real Kakoune). Catalog expansion is a one-line entry in
    kasane-plugin-sdk-macros/src/lint.rs.

Added — Plugin DX dogfooding suite (Issue #81, 2026-05-11)

The sprout dogfooding tracker (#81) closes 8/12 children with a focused
SDK + docs landing:

  • kasane_plugin_sdk::kak module (#87): idempotent Kakoune-command
    string builders — declare_user_mode (encodes try %[ … ]),
    declare_option, define_command (auto-selects balanced delimiter
    %{…}%[…]%(…)%<…> → quoted fallback), map,
    escape_arg, plus Scope and OptionKind enums. Encodes the correct
    idempotency idiom per command so plugin authors cannot accidentally
    pass -override to commands that don't accept it (the sprout
    regression).
  • kakoune_setup_effects! macro (#88): builds an Effects value
    sending each command as its own Command::SendKeys — failure
    isolation, unlike a single evaluate-commands %{ … } block which
    cascade-fails on the first error.
  • process_event! macro (#91): safe destructure for
    IoEvent::Process under the new variant non-exhaustive policy.
    Replaces the irrefutable let IoEvent::Process(p) = event; pattern
    that broke at the 0.5→0.6 Http variant addition.
  • keys::push_literal debug-asserts on \n (#86): Kakoune prompts
    reject newline; the dev-time panic surfaces the bug in tests.
  • keys::command doc-comment rewritten (#83): full escape table,
    <esc> mode side-effect note, <ret>-literal round-trip property,
    EvalCommand pointer with session-ready availability caveat.
  • examples/wasm/kakoune-bindings-demo (#84): worked example for
    the register-Kakoune-APIs-at-startup pattern (option, command,
    user-mode, key maps). Documents the -override flag asymmetry.
  • docs/abi-versioning.md (#85): two-axis versioning model
    (WIT ABI vs SDK semver), host's major.minor exact-match rule, bump
    decision table, plus Appendix A (variant non-exhaustive policy) and
    Appendix B (safe destructure macros).
  • docs/migration/0.5-to-0.6.md §1.2.1 + §1.5 (#82): 7 face→style
    helper renames table + IoEvent irrefutable bind / Style !Copy
    hoisting patterns.
  • docs/plugin-cookbook.md + docs/plugin-development.md:
    cookbook recipe and profiles-table row pointing plugin authors to the
    new kak::* + kakoune_setup_effects! pattern.

Added — Plugin command-error attribution Phase A (ADR-042)

Host-internal half of the plugin command-error observability protocol
(#90). Plugins can wrap their Kakoune-side emissions as

try %[ <cmd> ] catch %[
    info -title '__kasane_plugin_error__' %{ <plugin-id>%val{error} }
]

The state-apply layer recognises the reserved title, parses
(plugin-id, message) from the content, emits
tracing::warn!(plugin_id, message, …) with the attribution, and
suppresses the marker so it never reaches the user-visible UI. The
empirical Kakoune-side validation that motivated the design is
captured in ADR-042's Empirical validation section. Phase B (WIT 4.0.0
on-command-error-effects export, host auto-wrap) is RFC-tracked but
unimplemented.

Breaking — Plugin ABI 4.1.0 (ADR-041 + ADR-042 Decided, 2026-05-11)

WIT bumps from kasane:plugin@3.0.0@4.1.0 in two coordinated steps:

  • ADR-041
    (ABI 4.0.0, dd2fbe3a): eval-command(string) added to
    session-ready-command variant. Plugins can now issue command bodies
    directly at on_active_session_ready_effects without the
    <esc>:cmd<ret> keystroke-simulation wrapping.
  • ADR-042
    (ABI 4.1.0, 178eeedd + 858581db + cfc13952 + 4eb241ca): plugin
    command-error observability via the info_show marker pattern. Adds
    command-error record and on-command-error-effects export to WIT.
    Host-side state/apply.rs recognises the reserved title, parses
    (plugin-id, message), and routes to the originating plugin via
    PluginRuntime::deliver_command_error_batch. Plugins opt into host
    auto-wrap via [handlers] command_error_observability = true in the
    manifest; the host wraps every emitted Command::EvalCommand with
    try…catch so failures surface as attributed events.

Plugin migration:

  • Bump abi_version = "4.1.0" in kasane-plugin.toml.
  • Pin kasane-plugin-sdk = "0.7".
  • cargo component build — pure recompile, no source changes
    required. Plugins that want command-error observability opt in via
    the new manifest flag and (optionally) override the new export.

All bundled / fixture / example WASMs rebuilt against the new ABI
(23 manifests across examples/wasm/, kasane-wasm/fixtures/, and
kasane-wasm/bundled/).

Breaking — R2.x P7+P9 cascade: WireFace removed from public plugin API (2026-05-10)

Closes the post-ADR-031 visibility-tightening backlog from
roadmap.md §2.2. The wire-format-aware WireFace type is no
longer reachable from plugin_prelude and is #[doc(hidden)] pub
internally; plugin authors construct atoms / element styles via
the post-resolve Style type. See
docs/migration/0.6-to-0.7.md for
each surface's before/after.

Surfaces that changed type:

  • Element::text(s, face: WireFace)Element::text(s, style: Style). The auxiliary Element::text_with_style constructor is
    gone (its body absorbed into Element::text).
  • DisplayDirective::StyleInline { face: WireFace }
    { style: Style }. Same for StyleLine.
  • InlineOp::Style { face: WireFace }{ style: Style }.
  • CursorEffectOrn { face: WireFace }{ style: Style }. Same
    for SurfaceOrn.
  • Command::RegisterThemeTokens(Vec<(String, WireFace)>)
    Vec<(String, Style)>. The
    KakouneSafeCommand::register_theme_tokens(tokens) helper
    follows.
  • ColorResolver::resolve_face_colors[_linear](&WireFace) and the
    WireFace sync_defaults(&WireFace) are deleted; consumers use
    resolve_style_colors[_linear](&Style) and
    sync_defaults(&Style).
  • Atom::from_wire(WireFace, _) is pub(crate) (only the protocol
    parser and test_support::wire's cursor fixtures need the
    final_*-preserving path). Plugin code uses
    Atom::with_style(_, Style).
  • WireFace is no longer in kasane_core::plugin_prelude.
    Plugins that observed final_* resolution flags via WireFace
    now read them from UnresolvedStyle.final_fg / final_bg /
    final_style.

Changed — bridge.rs dispatch macros (R2.x P8) (2026-05-10)

Two new dispatch macros (dispatch_state_with_default!,
dispatch_inject_owner_contribution!) consolidate 10 hand-coded
sites in PluginBridge. Visible to plugin authors only as
slightly clearer inline-box / contribution / overlay panic
backtraces; no behavioural change.

Added — kasane.kdl auto-reload for plugins and settings (ADR-040)

Opt-in plugins.auto_reload #true makes edits to the plugins and
settings blocks in kasane.kdl apply live: kasane runs resolve,
rewrites plugins.lock, and live-swaps the plugin set without a
restart. Settings-only changes skip the lock update and refresh
plugins in place. Default is #false to preserve the existing
"resolve, then restart" workflow that CI scripts depend on. See
docs/using-plugins.md
for usage.

Fixed — Per-plugin teardown on hot-reload

  • Config::restart_required_diff now compares settings, surfacing a
    diagnostic for changes that were previously ignored silently.
  • PluginVariableStore records the owning plugin id when a plugin
    exposes a variable, and unload_plugin clears those entries so a
    hot-reloaded plugin can't observe the previous instance's values.
  • ProcessDispatcher::kill_all_for_plugin aborts every child process
    owned by an unloaded plugin and resets the per-plugin process slot
    count, fixing a leak where re-loaded plugins could hit
    MAX_PROCESSES_PER_PLUGIN for processes that no longer existed.

What's Changed

  • chore(release): update Homebrew formula to 0.5.0 by @github-actions[bot] in #55
  • chore(release): update kasane-bin to 0.5.0 by @github-actions[bot] in #56
  • deps(deps): update rust patch updates by @renovate[bot] in #54
  • deps(deps): update rust minor updates (fixed) by @Yus314 in #60
  • deps(deps): update softprops/action-gh-release action to v3 by @renovate[bot] in #57
  • ADR-031 closure: Style-native pipeline + WIT 2.0.0 by @Yus314 in #65
  • deps(deps): update rust patch updates by @renovate[bot] in #63
  • Post-closure cleanup: TUI benches + HostState Style + StyledLine scratch + Face → WireFace by @Yus314 in #66
  • deps(deps): update rust minor updates by @renovate[bot] in #64
  • test(core): cursor positioning golden snapshots (Phase 12) by @Yus314 in #69
  • feat(core): runtime assertion for ShadowCursor × InlineBox overlap by @Yus314 in #71
  • test(core): selection rendering golden snapshots (Phase 12) by @Yus314 in #70
  • fix(gui): drop zero-size raster glyphs to clear spurious L2 drops by @Yus314 in #67
  • feat(core): protocol::wire submodule for ADR-031 visibility migration by @Yus314 in #73
  • fix(wasm): widen epoch budget + surface trap as panic in tests by @Yus314 in #74
  • test(core): CJK / combining-mark / emoji golden snapshots (Phase 12) by @Yus314 in #68
  • docs(roadmap,examples): close Phase 10 paint_inline_box worked-example item by @Yus314 in #72
  • chore(release): v0.6.0 by @Yus314 in #77
  • chore(release): update kasane-bin to 0.6.0 by @github-actions[bot] in #79
  • deps(deps): update rust crate image-compare to 0.5 by @renovate[bot] in #76
  • chore(release): update Homebrew formula to 0.6.0 by @github-actions[bot] in #78
  • chore(nix): bump contrib/nixpkgs/package.nix to v0.6.0 by @Yus314 in #80

New Contributors

  • @github-actions[bot] made their first contribution in #55

Full Changelog: v0.5.0...v0.7.0