# Kasane Plugin API Reference This document is a reference for looking up the Kasane plugin API. For a quickstart guide to writing a working plugin, see [plugin-development.md](./plugin-development.md). For composition ordering and correctness conditions, see [semantics.md](./semantics.md). ## 0. Scope of the Plugin API The Kasane plugin API is primarily designed for **UI decoration, transformation, and extension**. Plugins construct Element trees based on state received from Kakoune and provide supplementary visual information to users. Side effects are issued indirectly via `Command`, and are limited to UI-side coordination such as sending keys to Kakoune, requesting redraws, inter-plugin messages, and timers. The following operations are currently outside the scope of the plugin API. | Out-of-scope operation | Reason | |---|---| | File system access | WASM is prohibited by the sandbox. Native is technically possible but lacks an async infrastructure | | Network communication | Same as above | | Text input widgets | No input elements in `Element`. Text editing is delegated to Kakoune by design | Native plugins run within the host process and can therefore technically use `std::process`, `std::fs`, etc. However, Plugin trait hook functions are called synchronously, so the plugin developer bears the design responsibility for avoiding main thread blocking. For WASM-specific constraints (feature gaps, runtime limits, missing state queries), see [§8 WASM Plugin Constraints](#8-wasm-plugin-constraints). Kasane's long-term strategy is to **make WASM the first-class distribution and execution path, with capabilities as close to native as possible**. Accordingly, native-only APIs are treated not as "permanent advantages" but as one of the following: - A provisional escape hatch not yet stably exposed via WIT - A host-integration API requiring redesign to achieve WASM parity - An API intentionally kept native-only based on security boundary decisions File system access is provided via WASI capability declarations (Phase P-1), and external process execution is provided via host-mediated `Command` + `IoEvent` (Phase P-2). See [ADR-019](./decisions/adr-019-plugin-io-infrastructure-hybrid-model.md) for design rationale. ## 1. Extension Points ### 1.1 Core Surfaces and Built-in Slots The core UI is structured around surfaces. The extension points available to plugins are declared by each surface. | SurfaceId | Surface | Description | |---|---|---| | `BUFFER` (0) | `KakouneBufferSurface` | Main buffer display | | `STATUS` (1) | `StatusBarSurface` | Status bar (rendered once per pane in multi-pane mode) | | `MENU` (2) | `MenuSurface` | Menu | | `INFO_BASE`+ (10+) | `InfoSurface` | Info popups | | `PLUGIN_BASE`+ (100+) | Plugin-defined | Plugin-provided surfaces | | SlotId | Position | Declaring Surface | |---|---|---| | `kasane.buffer.left` | Left of buffer | `KakouneBufferSurface` | | `kasane.buffer.right` | Right of buffer | `KakouneBufferSurface` | | `kasane.buffer.above` | Above buffer | `KakouneBufferSurface` | | `kasane.buffer.below` | Below buffer | `KakouneBufferSurface` | | `kasane.buffer.overlay` | Overlay on buffer | `KakouneBufferSurface` | | `kasane.status.above` | Above status bar | `StatusBarSurface` | | `kasane.status.left` | Left of status bar | `StatusBarSurface` | | `kasane.status.right` | Right of status bar | `StatusBarSurface` | ### 1.2 Choosing a Mechanism | Goal | Mechanism to use | |---|---| | Add UI at a predefined location | `contribute_to()` | | Decorate individual buffer lines | `annotate_line_with_ctx()` | | Apply face to individual cells, ranges, or columns; override cursor style | `render_ornaments()` (via `OrnamentBatch`) | | Display floating UI | `contribute_overlay_with_ctx()` | | Modify or replace existing UI appearance | `transform()` | | Transform individual menu items | `transform_menu_item()` | As a principle, prefer the least flexible mechanism that suffices. Do not use `transform()` if `contribute_to()` can achieve the goal. ### 1.2.1 Display Transformations and Display Units As described in `P-030..P-043` of [requirements.md](./requirements.md) and `Display Transformations and Display Units` in [semantics.md](./semantics.md), Kasane allows plugins to treat display transformations as first-class concepts. The **Display Transform API** (`display_directives()`) provides the first concrete implementation of this direction. Plugins declare `DisplayDirective` values describing how buffer lines map to display lines. The core builds a `DisplayMap` — an O(1) bidirectional mapping between buffer lines and display lines — and integrates it throughout the rendering pipeline (paint, cursor, input, patch). **Available `DisplayDirective` variants** (4 categories, 11 variants): | Category | Variant | Description | |---|---|---| | Spatial | `Fold { range, summary }` | Collapse a range of buffer lines into a single summary line | | Spatial | `Hide { range }` | Hide a range of buffer lines entirely | | InterLine | `InsertBefore { line, content, priority }` | Insert a full Element before a buffer line | | InterLine | `InsertAfter { line, content, priority }` | Insert a full Element after a buffer line | | Inline | `InsertInline { line, byte_offset, content, interaction }` | Insert inline content at a byte offset | | Inline | `HideInline { line, byte_range }` | Hide a byte range within a buffer line | | Inline | `StyleInline { line, byte_range, face }` | Apply face styling to a byte range | | Decoration | `StyleLine { line, face, z_order }` | Apply a background face to an entire line | | Decoration | `Gutter { line, side, content, priority }` | Add content to the gutter of a line | | Decoration | `VirtualText { line, position, content, priority }` | Add virtual text at end of line or right-aligned | | Decoration | `EditableVirtualText { after, content, editable_spans }` | Editable virtual text with shadow cursor support | **Key types:** - `DisplayMap`: bidirectional buffer↔display line mapping with `display_to_buffer()`, `buffer_to_display()`, `entry()`, `is_identity()` - `SourceMapping`: `BufferLine(usize)`, `LineRange(Range)`, `None` (virtual text) - `InteractionPolicy`: `Normal`, `ReadOnly` (clicks suppressed), `Skip` (navigation skips) - `SyntheticContent`: text and face for non-buffer display lines **Multi-plugin composition (P-031):** - Multiple plugins may contribute display directives simultaneously - Composition is deterministic via `resolve()`: Hide ranges are unioned, InsertAfter and InsertBefore lines accumulate, overlapping Folds are resolved by `(priority, plugin_id)` (higher wins) - Plugins declare priority via `display_directive_priority()` (default 0) - Folds that partially overlap hidden ranges are conservatively removed (protects summary integrity) - InsertAfter/InsertBefore targeting hidden or folded lines are suppressed **Constraints:** - Display-oriented navigation (Display Units, P-040..P-043) is implemented via `DisplayUnit`, `NavigationPolicy`, and `NavigationAction` (see §3.4.1) - Kakoune controls the viewport and cursor movement, so true code folding (where folded lines are skipped during navigation) is not possible; `Fold` is best suited for read-only summaries - `InsertAfter`/`InsertBefore` (virtual text) are the primary practical use cases; `InsertBefore` enables Gap 0 (before the first buffer line) **Destructive directives and recovery (ADR-030 §Level 4 + RFC-107a):** - `Hide` and `HideInline` are classified as Destructive. They are recoverable §10.2a-faithfully because the host maintains `UniversalRevealState`: pressing `` toggles a flag that drops every destructive directive *pre-algebra* in `collect_tagged_display_directives`. Decorations that would have been displaced by a destructive winner survive on reveal. - Plugins do not need to declare a `RecoveryWitness` for §10.2a anymore; raw `on_display` registration is sufficient. The `on_display_safe` / `on_display_witnessed` / `SafeDisplayDirective` surface remains for plugins that want compile-time guarantees or per-plugin recovery semantics, but is no longer required for faithfulness. - See `docs/roadmap/rfc-107a-universal-reveal-state.md` for the design discussion and `kasane-core-tests/tests/visual_faithfulness.rs` for the witnessed properties. See the previous `examples/virtual-text-demo/` artifact (moved to the future external `kasane-plugin-gallery` per δ-3 cleanup; recover from this repo's git history). For mechanisms not covered by DisplayDirective (overlay composition, element-level restructuring), plugins should use the existing combination of `contribute_to()`, `transform()`, `annotate_line_with_ctx()`, `contribute_overlay_with_ctx()`, and `Surface`. ### 1.2.2 Choosing a Plugin Model > `Plugin` + `HandlerRegistry` is the only native plugin authoring path. The framework wraps each `Plugin` impl in a [`PluginBridge`](#) at registration time and dispatches every extension point through the bridge's inherent methods. New extension points are added via `HandlerRegistry::on_X(...)` registrations. Native plugins implement the `Plugin` trait: | | `Plugin` (HandlerRegistry — public) | |---|---| | Registration | 2 methods + 1 associated type: `id()`, `State` type, `register()` with `HandlerRegistry` | | State ownership | Framework holds state; handlers are pure functions | | Capabilities | Auto-inferred from registered handlers | | Cache invalidation | Automatic via `PartialEq` comparison (generation counter) | | Salsa compatibility | State transitions are pure functions; future Salsa integration path | | Use cases | All native plugin scenarios — UI decoration, surfaces, workspace observation, host integration. Surface and workspace observation are reachable via `HandlerRegistry::on_surfaces` / `on_workspace_*` handlers and via the `Lifecycle`-supertrait blanket impl | In unit tests, register via `PluginRuntime::register()`. In a host binary, wrap it with `PluginBridge::new(...)` and pass it to `kasane::run_with_factories(...)`. ```rust use kasane_core::plugin_prelude::*; #[derive(Clone, Debug, PartialEq, Default)] struct MyState { counter: u32 } struct MyPlugin; impl Plugin for MyPlugin { type State = MyState; fn id(&self) -> PluginId { PluginId("my_plugin".into()) } fn register(&self, r: &mut HandlerRegistry) { r.declare_interests(DirtyFlags::BUFFER); r.on_state_changed_tier1(|state, _app, dirty| { if dirty.intersects(DirtyFlags::BUFFER) { (MyState { counter: state.counter + 1 }, KakouneSideEffects::default()) } else { (state.clone(), KakouneSideEffects::default()) } }); r.on_decorate_background(|state, line, _app, _ctx| { // ... return Option None }); } } // Unit-test registration: registry.register(MyPlugin); ``` ### 1.3 Composition Rules The composition order for extensions is as follows: 1. Build the seed default elements 2. Apply the transform chain in priority order (processing decoration and replacement in a unified manner) 3. Compose contributions and overlays For detailed semantics, see `Plugin Composition Semantics` in [semantics.md](./semantics.md). ### 1.4 Contribution (`contribute_to`) `contribute_to()` is the most constrained extension, contributing `Element`s to framework-provided extension points (`SlotId`). **Native:** ```rust fn contribute_to(&self, region: &SlotId, app: &AppView<'_>, _ctx: &ContributeContext) -> Option { if region != &SlotId::BUFFER_LEFT { return None; } Some(Contribution { element: Element::text("★", Style::default()), priority: 0, size_hint: ContribSizeHint::Auto, }) } ``` **WASM:** ```rust fn contribute_to(region: SlotId, _ctx: ContributeContext) -> Option { kasane_plugin_sdk::route_slot_ids!(region, { BUFFER_LEFT => { Some(Contribution { element: element_builder::create_text("★", face), priority: 0, size_hint: ContribSizeHint::Auto, }) }, }) } ``` `ContributeContext` provides layout-aware constraints. The main fields are `min_width` / `max_width` / `min_height` / `max_height`, where `None` represents unbounded. `Contribution` consists of `element`, `priority` (composition order), and `size_hint` (`Auto` / `Fixed(u16)` / `Flex(f32)`). The legacy `u8` constants from `slot::BUFFER_LEFT` through `slot::OVERLAY` remain in the `kasane_plugin_sdk::slot` module, but the canonical API uses first-class `SlotId`. Custom slots can be specified in both Native and WASM via `SlotId::new("...")` / `SlotId::Named("...".into())`. ### 1.5 Line Annotation (`annotate_line_with_ctx`) `annotate_line_with_ctx()` contributes gutter elements and backgrounds to individual buffer lines. **Native:** ```rust fn annotate_line_with_ctx(&self, line: usize, app: &AppView<'_>, _ctx: &AnnotateContext) -> Option { if line == app.cursor_line() as usize { Some(LineAnnotation { left_gutter: None, right_gutter: None, background: Some(BackgroundLayer { style: Style { bg: Brush::rgb(40, 40, 50), ..Style::default() }, z_order: 0, blend: BlendMode::Opaque, }), priority: 0, inline: None, }) } else { None } } ``` `LineAnnotation` consists of five fields: `left_gutter`, `right_gutter`, `background`, `priority` (controls gutter element ordering), and `inline` (byte-range inline decoration). `BackgroundLayer` has `face`, `z_order`, and `blend` (compositing mode); background contributions from multiple plugins are composited in `z_order` order. Gutter contributions are composited horizontally. #### Inline Decoration The `inline` field provides byte-range operations applied directly to buffer line atoms. This enables styling or hiding sub-ranges of a line without replacing the entire element tree. `InlineDecoration` contains a sorted list of `InlineOp`: - `InlineOp::Insert { at, content }` — Insert virtual text atoms at the given byte gap position - `InlineOp::Style { range, face }` — Override the face for the given byte range - `InlineOp::Hide { range }` — Hide the given byte range (omit from output) Ops are sorted by `sort_key()` — `(position, variant_order)` where Insert (0) sorts before Style/Hide (1) at the same position. Range-based ops (Style/Hide) must be non-overlapping. Multiple Insert ops at the same position are allowed and emitted in order. Insert ops inside a Hide range are still emitted (**S1 semantics**): the Hide omits the original buffer text, but any Insert whose `at` falls within the hidden range produces its virtual content. **Replace pattern** — Hide + Insert at the same position to substitute text: ```rust // Replace "def" (bytes 3..6) with "new" in "abcdefghi" InlineDecoration::new(vec![ InlineOp::Insert { at: 3, content: vec![Atom { face: red_face, contents: "new".into() }] }, InlineOp::Hide { range: 3..6 }, ]) // Result: "abc" + "new"(red) + "ghi" ``` **Example** — style bytes 6..11 ("world") in red, hide bytes 0..2 ("he"): ```rust fn annotate_line_with_ctx(&self, line: usize, app: &AppView<'_>, _ctx: &AnnotateContext) -> Option { Some(LineAnnotation { left_gutter: None, right_gutter: None, background: None, priority: 0, inline: Some(InlineDecoration::new(vec![ InlineOp::Hide { range: 0..2 }, InlineOp::Style { range: 6..11, style: Style { fg: Brush::Named(NamedColor::Red), ..Style::default() }, }, ])), }) } ``` Byte ranges operate on UTF-8 byte offsets within the line's atom contents. Phase 1 constraint: only one plugin may provide inline decoration per line. ### 1.6 Overlay (`contribute_overlay_with_ctx`) `contribute_overlay_with_ctx()` provides floating elements that are overlaid outside the normal layout flow. **Native:** ```rust fn contribute_overlay_with_ctx(&self, app: &AppView<'_>, _ctx: &OverlayContext) -> Option { Some(OverlayContribution { element: Element::container(child, style), anchor: OverlayAnchor::AnchorPoint { coord, prefer_above: true, avoid: vec![] }, z_index: 0, plugin_id: self.id(), }) } ``` **WASM:** ```rust fn contribute_overlay_v2(_ctx: OverlayContext) -> Option { Some(OverlayContribution { element: element_builder::create_container_styled(child, ...), anchor: OverlayAnchor::Absolute(AbsoluteAnchor { x: 10, y: 5, w: 30, h: 10 }), z_index: 0, }) } ``` `OverlayContribution` consists of `element`, `anchor`, `z_index`, and `plugin_id` (used for deterministic tie-breaking). There are two types of `OverlayAnchor`: - `Absolute { x, y, w, h }`: Absolute position in screen coordinates - `AnchorPoint { coord, prefer_above, avoid }`: Kakoune-compatible anchor-based positioning ### 1.7 Transform (`transform`) `transform()` is a unified mechanism that receives a `TransformSubject` (either an `Element` or an `Overlay`), transforms it, and returns the result. It serves as both decoration (formerly Decorator) and replacement (formerly Replacement). For non-overlay targets (Buffer, StatusBar), the subject is `Element`; for overlay targets (Menu, Info), it is `Overlay` (Element + OverlayAnchor), allowing plugins to modify overlay position and size. **Native:** ```rust fn transform(&self, target: &TransformTarget, subject: TransformSubject, app: &AppView<'_>, _ctx: &TransformContext) -> TransformSubject { subject.map_element(|element| { if *target == TransformTarget::BUFFER { Element::container(element, Style::default()) } else { element } }) } fn transform_priority(&self) -> i16 { 100 } ``` **WASM:** ```rust fn transform(target: TransformTarget, subject: TransformSubject, _ctx: TransformContext) -> TransformSubject { match subject { TransformSubject::Element(element) => { TransformSubject::Element(container(element).border(BorderLineStyle::Single).build()) } other => other, } } fn transform_priority() -> i16 { 100 } ``` `TransformTarget` includes `Buffer`, `StatusBar`, `Menu`, `Info`, and others. **Native (with target specification):** ```rust r.on_transform_for(0, &[TransformTarget::BUFFER, TransformTarget::STATUS_BAR], |state, target, app, ctx| { if *target == TransformTarget::STATUS_BAR { ElementPatch::Append { element: Element::text("extra", Style::default()) } } else { ElementPatch::Identity } }); ``` `on_transform_for()` specifies which `TransformTarget`s the handler applies to. This populates `CapabilityDescriptor::transform_targets`, enabling `may_interfere()` to detect transform target overlap between plugins. Use `on_transform()` (without targets) when the handler applies to all targets. **WASM (declarative patch):** ```rust // In define_plugin!: transform_patch(target, ctx) { if target == "kasane.status-bar" { vec![ElementPatchOp::Prepend(text("[K] ", default_face()))] } else { vec![] // empty = identity (no patch) } }, ``` `transform_patch` returns `Vec` instead of imperatively transforming the subject. Pure patches are Salsa-memoizable. Guidelines: - Do not assume the internal structure of the received `Element` - For lightweight decoration, prefer wrapping the `Element` as-is - Full replacement is also performed via `transform()` (ignore the received element and return a new one) - Use `transform_priority()` to control the application order - Declare `handlers.transform_targets` in the manifest for interference detection ### 1.8 Menu Transform (`transform_menu_item`) `transform_menu_item()` is a per-menu-item transformation corresponding to the `MENU_TRANSFORM` capability. Use it when you want to locally transform the label or style of individual items. If you need to replace the entire menu structure, use `transform()` with `TransformTarget::MENU`. ### 1.10 Display Transformation API The display transformation API allows plugins to restructure the display without falsifying protocol truth. `display_directives()` returns a `Vec` describing how buffer lines map to display lines. Design principles: - Transformations do not falsify protocol truth — they are display policy - The core builds a `DisplayMap` providing source mapping and interaction policy - When the inverse mapping to source is weak, read-only or restricted interaction is applied automatically - `InsertAfter` virtual text lines get `InteractionPolicy::ReadOnly` and `SourceMapping::None` - `Fold` summary lines get `InteractionPolicy::ReadOnly` and `SourceMapping::LineRange` ```rust fn display_directives(&self, state: &Self::State, app: &AppView<'_>) -> Vec { vec![DisplayDirective::InsertAfter { line: 2, content: Element::text( " ⚠ TODO — address before merge", Style { fg: Brush::Named(NamedColor::Yellow), ..Style::default() }, ), priority: 0, }] } ``` The `DisplayMap` is integrated into: paint (buffer rendering), cursor positioning (`buffer_to_display`), mouse input (`display_to_buffer` with interaction policy check), and the patch optimization layer. The display unit model (P-040..P-043) is implemented; see §3.4.1 for navigation API. Future extension: WASM WIT `display-directive-priority` function. ### 1.11 Projection API (`define_projection`) Projections are named display transformation strategies registered via `HandlerRegistry::define_projection()`. Two categories: | Category | Behavior | Example | |---|---|---| | **Structural** | Mutually exclusive — at most one active | Semantic Zoom, Focus Mode | | **Additive** | Composable — any number active | Error Lens, Diff Marks | Activation is managed via `ProjectionPolicyState` (in `AppState::config`). Structural projections are activated with `set_structural(Some(id))`, additive with `toggle_additive(id)`. Directives from inactive projections are not collected. ```rust r.define_projection( ProjectionDescriptor { id: ProjectionId::new("my-plugin.my-projection"), name: "My Projection".to_string(), category: ProjectionCategory::Structural, priority: -50, }, |state: &MyState, app: &AppView<'_>| -> Vec { // Return directives based on plugin state and app state vec![] }, ); ``` Priority bands: Structural -500..0, Ambient/Legacy 0..500, Additive 500..1000. Per-projection fold toggle state is tracked via `FoldToggleState`, accessible through `app.projection_policy().fold_state_for(&id)`. The existing `BuiltinFoldPlugin` handles ToggleFold interaction for any projection. The built-in `kasane.semantic-zoom` projection (`kasane-core/src/plugin/semantic_zoom/`) demonstrates the full projection pattern: state-driven directive generation, key map bindings, dual strategy dispatch (syntax-aware with tree-sitter fallback to indent-based). ### 1.12 Syntax Provider API `SyntaxProvider` (in `kasane-core/src/syntax/`) provides AST-level information to plugins. The active provider is set on `AppState::runtime.syntax_provider` and accessible via `AppView::syntax_provider()`. Key trait methods: | Method | Description | |---|---| | `generation()` | Monotonic counter, incremented on re-parse | | `fold_ranges()` | Foldable line ranges from AST structure | | `declarations()` | Function/struct/enum/trait declarations with name, kind, signature, and body ranges | | `signature_summary(line)` | Human-readable signature text for the declaration at the given line | | `scopes_at(line, byte)` | Scope chain at a position (innermost last) | | `nodes_in_range(range, kind)` | Named AST nodes within a byte range | The `kasane-syntax` crate (feature-gated via `--features syntax`) provides `TreeSitterProvider`, which implements `SyntaxProvider` backed by tree-sitter. Grammar `.so` files are loaded from `$XDG_DATA_HOME/kasane/grammars/` or `$XDG_DATA_HOME/kak-tree-sitter/grammars/`. Declaration queries are bundled for Rust, Python, Go, and TypeScript. ## 2. Element API ### 2.1 Element variants | Type | Purpose | WASM builder | Native | |---|---|---|---| | `Text` | Text + style | `create_text(content, style)` | `Element::text(s, style)` | | `StyledLine` | Atom sequence | `create_styled_line(atoms)` | `Element::styled_line(line)` | | `Flex` (Column) | Vertical layout | `create_column(children)` / `create_column_flex(entries, gap)` | `Element::column(children)` | | `Flex` (Row) | Horizontal layout | `create_row(children)` / `create_row_flex(entries, gap)` | `Element::row(children)` | | `Grid` | 2D table | `create_grid(cols, children, col_gap, row_gap)` | `Element::grid(columns, children)` | | `Container` | border/shadow/padding | `create_container(...)` / `create_container_styled(...)` | `Element::container(child, style)` | | `Stack` | Z-axis stacking | `create_stack(base, overlays)` | `Element::stack(base, overlays)` | | `Scrollable` | Scrollable region | `create_scrollable(child, offset, vertical)` | `Element::Scrollable { ... }` | | `Interactive` | Mouse hit test | `create_interactive(child, id)` | `Element::Interactive { child, id }` | | `Image` | Raster image | `create_image(source, w, h, fit, opacity)` | `Element::image(source, w, h)` | | `Empty` | Empty element | `create_empty()` | `Element::Empty` | | `BufferRef` | Buffer line reference | Host-internal only | `Element::buffer_ref(range)` | ### 2.2 WASM element-builder API All functions are imported from the `element_builder` module. The returned `ElementHandle` is valid only within the current plugin invocation scope. ```rust use kasane::plugin::element_builder; let text = element_builder::create_text("hello", face); let col = element_builder::create_column(&[text]); let container = element_builder::create_container( col, Some(BorderLineStyle::Single), false, Edges { top: 0, right: 1, bottom: 0, left: 1 }, ); ``` For proportional distribution, use `create_column_flex` / `create_row_flex` with `FlexEntry { child, flex }`. #### Image element (WIT v0.20.0) `create_image` constructs a raster image element. Images are rendered natively on the GPU backend; the TUI backend renders a low-resolution approximation using Unicode halfblock characters (`▀`), with each cell representing two pixel rows (fg = top, bg = bottom). If image decoding fails, the TUI falls back to a text placeholder (`[IMAGE: filename]` or `[IMAGE: W×H]`). **`ImageSource`:** | Variant | Description | |---|---| | `file-path(String)` | Path to an image file on disk | | `rgba-data { data: Vec, width: u32, height: u32 }` | Pre-decoded RGBA pixel data (4 bytes per pixel) | **`ImageFit`:** | Value | Description | |---|---| | `contain` (default) | Scale to fit within the area, preserving aspect ratio (letterboxing) | | `cover` | Scale to cover the entire area, preserving aspect ratio (cropping) | | `fill` | Stretch to fill the area exactly (may distort) | Size is specified in cells (width, height). `opacity` ranges from 0.0 (transparent) to 1.0 (opaque). ### 2.3 Native element construction ```rust use kasane_core::plugin_prelude::*; let text = Element::text("hello", Style::default()); let col = Element::column(vec![ FlexChild::fixed(text), FlexChild::flexible(Element::Empty, 1.0), ]); ``` `FlexChild::fixed(element)` is fixed, and `FlexChild::flexible(element, factor)` is proportionally distributed. ## 3. State Access and Events ### 3.1 AppState overview Native plugins access application state through `&AppView<'_>`, a zero-cost wrapper providing method-based accessors (e.g. `app.cursor_line()`, `app.lines()`, `app.cols()`). | Field | Type | Description | |---|---|---| | `lines` | `Vec` | Buffer lines | | `cursor_pos` | `Coord` | Cursor position | | `status_line` | `Line` | Status bar | | `menu` | `Option` | Menu state | | `infos` | `Vec` | Info popups | | `cols`, `rows` | `u16` | Terminal size | | `focused` | `bool` | Focus state | Dirty flags primarily notify the following observable aspects: | Flag | Description | |---|---| | `BUFFER` | Buffer lines and cursor | | `STATUS` | Status bar | | `MENU_STRUCTURE` | Menu structure | | `MENU_SELECTION` | Menu selection | | `INFO` | Info popups | | `OPTIONS` | UI options | For semantic classification, see [semantics.md](./semantics.md). ### 3.2 WASM host-state API `kasane::plugin::host_state` provides a tiered read API. **Basic state (Tier 0):** | Function | Return type | |---|---| | `get_cursor_line()` | `s32` | | `get_cursor_col()` | `s32` | | `get_line_count()` | `u32` | | `get_cols()` | `u16` | | `get_rows()` | `u16` | | `is_focused()` | `bool` | **Buffer lines (Tier 0.5):** | Function | Return type | |---|---| | `get_line_text(line)` | `Option` | | `get_lines_text(start, end)` | `Vec` | | `get_lines_atoms(start, end)` | `Vec>` | | `is_line_dirty(line)` | `bool` | `get_lines_text` and `get_lines_atoms` (WIT v0.18.0) retrieve all lines in the `[start, end)` range in a single host call, avoiding per-line round-trip overhead. **Status bar (Tier 1):** | Function | Return type | |---|---| | `get_status_prompt()` | `Vec` | | `get_status_content()` | `Vec` | | `get_status_line()` | `Vec` | | `get_status_mode_line()` | `Vec` | | `get_status_default_style()` | `Style` | **Menu/Info state (Tier 2):** | Function | Return type | |---|---| | `has_menu()` | `bool` | | `get_menu_item_count()` | `u32` | | `get_menu_item(index)` | `Option>` | | `get_menu_selected()` | `s32` | | `has_info()` | `bool` | | `get_info_count()` | `u32` | **General state (Tier 3):** | Function | Return type | |---|---| | `get_ui_option(key)` | `Option` | | `get_cursor_mode()` | `u8` | | `get_widget_columns()` | `u16` | | `get_default_style()` | `Style` | | `get_padding_style()` | `Style` | **Multi-cursor (Tier 4):** | Function | Return type | |---|---| | `get_cursor_count()` | `u32` | | `get_secondary_cursor_count()` | `u32` | | `get_secondary_cursor(index)` | `Option` | **Typed Settings (Tier 5):** | Function | Return type | |---|---| | `get_setting_bool(key)` | `Option` | | `get_setting_integer(key)` | `Option` | | `get_setting_float(key)` | `Option` | | `get_setting_string(key)` | `Option` | | `get_config_string(key)` | `Option` *(deprecated, use typed settings)* | **Info details (Tier 6):** | Function | Return type | |---|---| | `get_info_title(index)` | `Option>` | | `get_info_content(index)` | `Option>>` | | `get_info_style(index)` | `Option` | | `get_info_anchor(index)` | `Option` | **Menu details (Tier 7):** | Function | Return type | |---|---| | `get_menu_anchor()` | `Option` | | `get_menu_mode()` | `Option` | | `get_menu_style()` | `Option