dev:scriptref:core:panes

smudgy:core — Panes

Generated from smudgy v0.5.4-dev (smudgy-core.d.ts @ a1c9d1939ffb). Index: scriptref.

Session panes: split named panes off the main pane (or off each other) and write lines into them. Panes host widgets (see widgets) and, unless created widgets-only, a terminal; a pane can also carry its own input line whose submissions go to your handler.

Pane

export interface Pane {
  readonly name: string;
  readonly kind: "terminal" | "widgets";
  readonly isMain: boolean;
  readonly created?: boolean;
  echo(text: string | StyledText): void;
  echo(text: TemplateStringsArray, ...values: unknown[]): void;
  clear(): void;
  close(): void;
  readonly input: InputHandle | undefined;
  split<D extends SplitDirection>(direction: D, spec: PaneSpec<D>): Pane;
  addTab(spec: TabPaneSpec): Pane;
  hide(): void;
  show(): void;
  readonly isHidden: boolean;
  setFontSize(px: number | null): void;
  readonly fontSize: number | undefined;
  resize(size: { width?: number; height?: number }): void;
  readonly size: { width: number; height: number } | undefined;
  relocate<D extends SplitDirection>(
    direction: D,
    reference?: Pane | string,
    size?: RelocateSize<D>,
  ): void;
  groupWith(reference: Pane, options?: GroupWithOptions): void;
  select(): void;
  tearOut(opts?: { width?: number; height?: number }): void;
  swap(otherPane: Pane): void;
}

A handle to one session pane. Panes are keyed by name: split() or addTab() with an existing name returns that pane. Most of the spec is then ignored, with two exceptions. An explicit titleBar updates the pane's policy. And input is part of what the pane is: asking for one on an existing pane that has none throws (close it first), while re-splitting a pane that has one re-registers its onSubmit (placeholder changes are ignored). A pane closes when close() is called, when the session ends, or when no script re-claims it during a reload; either creation call naming it during the reload keeps it, placement untouched. A later creation call with the same name recreates the pane and re-attaches its widgets.

import { session, createTrigger, line } from "smudgy:core";
// A chat pane above the main terminal; clan tells route into it.
const chat = session.mainPane.split("top", { name: "Chat", height: 100 });
createTrigger(/tells your clan '/, () => line.redirect(chat));
  • name — The pane's name in its display case.
  • kind — Whether this pane has a terminal ("terminal") or is widgets-only ("widgets"). Every pane can host widgets; the main pane is always "terminal".
  • createdfalse when split() returned an already-existing pane.
  • echo — Write whole lines into this pane's terminal. Throws on widgets-only panes. Takes styled text too, and works directly as a template tag.
  • clear — Clear this pane's terminal scrollback (works on main). Throws on widgets-only panes.
  • close — Close this pane. Throws on the main pane; safe to repeat otherwise.
  • input — This pane's own input line, or undefined for panes created without one (see PaneInputSpec). The same handle as InputHandle, addressed at this pane: its text, focus, masking, completion words, and history are all the pane input's own, independent of the main input's. On the main pane this is undefined too; its input is Session.input.
  • split — Split a new pane off this one (get-or-create by name; an explicit def-state field — titleBar, hidden, fontSize — also updates an existing pane, titleBar/fontSize including main's).
  • addTab — Create a pane as a tab in this pane's current group (get-or-create by name). New panes are inserted immediately after this pane and start unselected by default. Existing panes keep their placement.
  • hide — Hide this pane — the title-bar eyeball, scripted. A soft display state: the pane keeps running, widgets stay mounted, and routed lines keep landing in its scrollback. Throws on main (the user's eyeball owns main's visibility).
  • show — Show this pane (the eyeball's other half). Throws on main.
  • isHidden — The eyeball's toggle state — never effective visibility: a hidden pane still renders, veiled, while the toolbar is expanded, and a window whose every pane is hidden shows them all rather than go blank. Reads are live, including through foreign session handles.
  • setFontSize — Set (or with null clear) this pane's terminal font override in px (8–40; out of range throws). Scrollback text only — input lines stay on the global setting. Allowed on main, as a per-session override of the user's setting; that one additionally requires the change-display capability.
  • fontSize — This pane's font override in px, or undefined while following the global setting.
  • resize — Resize this pane in px. Each given dimension adjusts the nearest divider on that axis, which becomes script-owned until the user drags it again — last writer wins, in both directions. Best-effort per axis: a pane already spanning its cluster on an axis is left alone there. Throws on main (resize the sibling script pane instead).
  • size — The pane's last laid-out size in logical px, or undefined before the first layout report. A hidden pane keeps its last laid-out size.
  • relocate — Move this pane next to reference (default: the session's main pane). The direction reads exactly like split's — where this pane lands relative to the reference: chat.relocate('left') is the placement mainPane.split('left', …) would have produced. The move follows the reference across windows, so relocating onto a pane in a torn-out window re-docks there. Throws on main, on another session's Pane, and on this pane itself.
  • groupWith — Move or reorder this pane as a tab in reference's group. Main panes and same-server foreign-session references are allowed.
  • select — Select this pane's tab and make its session active without requesting keyboard focus. Selecting a hidden pane does not reveal it.
  • tearOut — Move this pane into a fresh window of its own — the drag tear-out, scripted. Windows stay anonymous: there is no window handle, the window closes when its last pane leaves it, and re-docking is a Pane.relocate onto a pane elsewhere. width/height size the new window (floored by the window minimum); omitted dimensions follow the pane's current size. Throws on main.
  • swap — Exchange this pane's position with another pane. Works across same-server sessions and windows; destination split geometry stays, pane state travels with pane identity, and no window activation or input focus is requested.

PaneRegistry

export type PaneRegistry = PaneRegistryMethods & { readonly [name: string]: Pane | undefined };

A pane registry with both method and property access (panes.get("chat") and panes.chat).

PaneRegistryMethods

export interface PaneRegistryMethods {
  get(name: string): Pane | undefined;
  list(): Pane[];
  exists(name: string): boolean;
}

A session's pane registry: get/list/exists cover panes in the caller's namespace (plus main), and dot access reaches any name (session.panes.chat). The same lookup surface works on a same-server foreign session handle.

PaneSpec

export type PaneSpec<D extends SplitDirection> = PaneSpecBase &
(D extends "left" | "right"
  ? { width?: number; height?: never }
  : { height?: number; width?: never });

The spec for Pane.split. Give the new pane's starting size in pixels along the split axis: width when splitting left/right, height when splitting top/bottom. The user can resize it afterwards.

PaneSpecBase

export interface PaneSpecBase {
  name: string;
  terminal?: boolean;
  titleBar?: TitleBarSpec;
  hidden?: boolean;
  fontSize?: number;
  input?: PaneInputSpec;
}

The direction-independent half of the spec for Pane.split.

  • name — Required. Names are case-insensitive (display case is preserved) and namespaced per package. Up to 64 printable characters; main, get, list, exists and then are reserved.
  • terminal — Default true. Pass false for a widgets-only pane with no terminal; echo/clear throw on it. Every pane can host widgets either way.
  • titleBar — Default 'normal'. Also applies to an existing pane: either creation call naming it (including 'main') with an explicit titleBar updates its policy.
  • hidden — Start the pane hidden — the title-bar eyeball's toggle, pre-set — so a reveal-on-event pane never flashes at load; show() (or the user's eyeball) reveals it. Explicit on an existing pane it updates the toggle; omitted, the current state — including the user's own toggle — is kept across reloads. Not allowed on main.
  • fontSize — This pane's terminal font size in px (8–40; out of range throws). Omitted, the pane follows the global setting. Explicit on an existing pane it updates the override; reverting is Pane.setFontSize with null. Scrollback text only — input lines stay on the global setting.
  • input — Give the pane its own input line (see PaneInputSpec). Part of what the pane is, like terminal: a creation call naming an existing pane that has no input while asking for one throws (close it first). Works on either pane kind, including a same-server session reached through sessions. Re-claiming with the same spec re-registers onSubmit, which is also how a handler comes back after your script reloads.

PaneInputSpec

export interface PaneInputSpec {
  onSubmit: (text: string) => void;
  placeholder?: string;
}

A pane's own input line (see PaneSpecBase.input). What the user submits there goes to your onSubmit handler and nowhere else: nothing is sent, matched against aliases, or echoed unless the handler does it, and the main input's history is untouched. session.send(text) inside the handler reproduces normal typed-command behavior.

import { session } from "smudgy:core";
// A chat pane whose input auto-prefixes the channel.
session.mainPane.split("right", {
  name: "Chat",
  width: 300,
  input: { onSubmit: (text) => session.send(`gt ${text}`), placeholder: "group tell..." },
});
  • onSubmit — Receives each submitted line. The text is yours alone: nothing is sent to the server, matched against aliases, or echoed unless you do it here, and the main input's history never records it.
  • placeholder — Hint text shown while the input is empty.

TabPaneSpec

export type TabPaneSpec = PaneSpecBase & {
  selected?: boolean;
  width?: never;
  height?: never;
};

The spec for Pane.addTab.

TabPosition

export type TabPosition = "before" | "after" | "end";

GroupWithOptions

export interface GroupWithOptions {
  position?: TabPosition;
  selected?: boolean;
}
  • position — Default "after". "end" appends to the reference's group.
  • selected — Default false. Select the moved pane after grouping it.

RelocateSize

export type RelocateSize<D extends SplitDirection> = D extends "left" | "right"
? { width?: number; height?: never }
: { height?: number; width?: never };

The optional extent for Pane.relocate, keyed to the split axis exactly like a split's initial size.

SplitDirection

export type SplitDirection = "left" | "right" | "top" | "bottom";

Which side of the pane you split from the new pane appears on.

TitleBarSpec

export type TitleBarSpec = "normal" | "always-show";

When a pane's title bar (its header, which is also its drag handle) is shown. 'normal' follows the global distraction-free rule: headers show while the window's toolbar is expanded, or when the “hide panel headers” setting is off. 'always-show' keeps the header visible regardless. A pane without a visible header cannot be drag-rearranged; dividers still resize it.

layout

export const layout: {
  save(name: string): void;
  apply(name: string): void;
  list(): string[];
};

Named workspace layouts for the current session's server. A layout is a saved snapshot of the windows that hold at least one of this server's panes – their splits, tab groups, sizes, and pane positions – stored under the server and addressed by name. Names are case-insensitive: "Combat" and "combat" are the same layout.

apply rearranges only what already exists: live panes of this session's server move into the saved arrangement, panes the layout doesn't mention keep riding with their groups, and slots for panes or sessions that aren't open are held open for them to fill later. It never opens or closes sessions, never prompts, and never creates, closes, moves, or resizes app windows – those are user actions, available through the Layouts toolbar menu.

Layouts exist so users' saved arrangements win. split() sizes and placements are creation defaults, while an explicit pane.resize() is imperative intent that overrides the user's saved geometry – exactly as a user divider drag would. So resizing panes at load time is an anti-pattern: it permanently defeats the sizes users saved. Use split defaults at creation and reserve resize for genuine runtime reactions; switch whole arrangements with layout.apply.

Every layout method requires both the panes and session: ["reach-others"] capabilities: rearranging the workspace reaches every window showing this server, not just the panes this script made.

  • save — Saves the current arrangement of this server's windows as name, replacing any layout the name (case-insensitively) already refers to. Only open panes are captured; a slot whose session is closed is not part of the snapshot. The snapshot is taken immediately, but the disk write is deferred and best-effort: rapid saves of the same name coalesce into one write (the latest snapshot wins), and a crash can lose a save made moments before. Cheap to call at gameplay rates.
  • apply — Applies the saved layout name to this server's live windows. Throws when no such layout exists. The apply itself is asynchronous and best-effort: a layout that no longer exists by the time it runs, or one that does not reference this server, does nothing. Safe to call at gameplay rates – switching layouts writes nothing to disk.
  • list — The saved layout names for this server, sorted.

Pane events: smudgy:events/pane

visibility

export const visibility: EventConsumer<{
  pane: string;
  hidden: boolean;
}>;

Fires on every actual visibility toggle of a pane — the user's title-bar eyeball and scripted hide()/show() alike (including the main pane, which only the user can toggle). pane is the pane's display-cased name, resolvable in your own namespace; hidden is the new toggle state. Subscribing requires the panes capability.

resize

export const resize: EventConsumer<{
  pane: string;
  width: number;
  height: number;
}>;

Fires when a pane's laid-out size settles on a new value — after a divider drag comes to rest, a window resize, or a scripted resize(); never per drag frame. width/height are logical px, the same values Pane.size reads. Subscribing requires the panes capability.


Script API reference · ← smudgy:core — Saved automations · smudgy:widgets — Overview → · Scripting manual