Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,8 @@ default_tab_template {
shortcut_prefix "⌘"
active_width "24"
align "center" // "center" slides to keep the active tab centered; "left" anchors the row (all-fit only)
reorder "false" // drag a tab to reorder; "true" also needs RunActionsAsUser
close_button "true" // stamps a clickable close button on each tab; "false" to hide
close_button_color "theme" // glyph color: "theme" (alert red) / "fg" (white) / "red" / "#rrggbb"
scroll "tab" // mouse wheel: "tab" (default) switch tabs / "pane" walk panes across tabs / "off"
scroll_cooldown_ms "40" // ms between wheel steps; higher = less sensitive, "0" = one step per event
tab_gap "2" // cleared columns between tab blocks; "0" packs them flush
gradient "sheen" // pane fill sweep: "sheen" (default) / "weave" (alternating rows) / "off" (flat)
gradient_shape "linear" // sweep geometry: "linear" (default) / "radial" (circular, from each block's center)
Expand All @@ -115,10 +112,6 @@ Contributors hacking on the plugin [build from source](#build-from-source) and p
>
> **`tab_gap` — space between tabs.** Leaves the given number of cleared columns between adjacent tab blocks so the boundary between screens reads clearly (default `2`). Set `0` to pack the blocks flush.
>
> **`scroll` — mouse wheel navigation.** Selects what the wheel does over the bar (zellij scroll events carry no position, so the gesture is bar-wide, not tied to a specific tab). `tab` (default) switches tabs — scroll up = next, scroll down = previous, following zellij's stock tab-bar direction but **wrapping** at the ends (first ↔ last) instead of clamping. `pane` instead walks the **focused pane** forward / backward in reading order (top→bottom, then left→right), crossing tab boundaries — stepping past a tab's last pane jumps to the next tab's first pane, and back — wrapping globally; the focus is absolute, so the bar's highlight follows correctly. `off` leaves the wheel inert. The wheel is rate-limited by `scroll_cooldown_ms` (below) so a stepless device does not race through tabs; the reported line count is otherwise ignored (each event is one notch). No extra permission is needed beyond the default set, so existing installs gain this on update without a re-grant.
>
> **`scroll_cooldown_ms` — wheel sensitivity.** The cooldown window, in milliseconds, between wheel navigation steps (default `40`). A stepless pointing device — an Apple Magic Mouse, a trackpad — reports a single flick as a *stream* of scroll events, so the original one-event-per-step behavior raced through several tabs (or panes) at once. zellij hands the plugin only `ScrollUp`/`ScrollDown` with no device identity, so the two kinds of mouse can't be told apart by hardware — instead the wheel is rate-limited by *timing*: the first event navigates immediately and opens the window, and any event arriving within `scroll_cooldown_ms` of it is dropped. So a fast flick collapses to about one step per window, while a deliberate, well-spaced notch (its window long since elapsed) always steps at once — responsive on a notched wheel mouse, damped on a trackpad, with no added latency on that first event. Raise it to damp the wheel further; set `0` to disable the limiter and restore the original one-step-per-event feel. Ignored when `scroll "off"`. No extra permission needed.
>
> **`gradient` — per-pane fill sweep.** `sheen` (default) sweeps each pane block's fill from its base color toward a luminance-shifted shade (lighter for dark themes, darker for light ones); `weave` alternates the sweep direction on each half-block pixel row for a woven texture. The focus ring, labels, and the `⌘N` badge stay solid on top, so readability is unchanged. Set `off` for flat fills.
>
> **`gradient_shape` / `gradient_angle` / `gradient_radial` — sweep direction.** These steer the `sheen`/`weave` sweep (they have no effect when `gradient "off"`). `gradient_shape` is `linear` (default, a straight sweep) or `radial` (a circular sweep from each pane block's center). For `linear`, `gradient_angle` sets the **perceived on-screen** direction in whole degrees over `[0, 360)`: `0` left→right (the v0.5 look), `90` top→bottom, `180` right→left, `270` bottom→top, and any angle in between for a diagonal — out-of-range or non-integer values fall back to `0`. For `radial`, `gradient_radial` chooses `outward` (default, base fill at the center easing to the stop at the edge) or `inward` (the reverse). Because each half-block pixel is already ≈ square, angles read as the true on-screen angle — `45` is a real 45°, not skewed by the terminal cell's 1:2 aspect.
Expand All @@ -127,8 +120,6 @@ Contributors hacking on the plugin [build from source](#build-from-source) and p
>
> **`perspective` — lift the active tab with depth.** When `true` (default) **and** the bar is at least **4 rows tall**, every inactive tab recedes by one row — a half-row of terminal background inset at its top and bottom — while the active tab fills the full height, so the selected tab appears to float forward. The height comes from the layout's `pane size=N`, which the plugin can only read, not set: bump the tab-bar pane to `size=4` (or more) to see the effect. Below 4 rows the option is a no-op (every tab fills the bar), and `false` always renders every tab at full height. Pairs naturally with `inactive_dim` — color recede plus depth recede. The bar renders nothing if it is given fewer than 3 rows (the minimap needs that floor to stay legible).
>
> **Enabling `reorder`** requests a third permission, `RunActionsAsUser` (for the `MoveTabByTabId` action a tab drag performs). Granting is all-or-nothing for tab-template plugins, so when you set `reorder "true"` you must **re-run step 2** (the grant prompt then lists all three permissions) and restart — otherwise the bar freezes with no prompt. Left at the default (`false`), the plugin requests only the two permissions above, so an existing install keeps working unchanged across updates.
>
> **`close_button` — click to close a tab.** When `true` (the default), a tab stamps a small close glyph (the Nerd Font *close-circle*, or a plain `×` where your terminal runs zellij's simplified UI without a Nerd Font) in its **top-right corner**; left-clicking exactly that cell closes the tab (via `close_tab_with_index`, which falls under the existing `ChangeApplicationState` grant — no re-grant needed). The glyph appears on the **active tab** — and, when the `perspective` depth cue is off, on **every** tab; under perspective the inactive tabs recede, where a corner glyph reads unbalanced, so they carry none. It only appears on blocks wide enough to draw a per-pane minimap, and never on the **last** remaining tab, so you can't close the bar out of existence. A click anywhere else on the block keeps its usual behavior (switch tab / focus the clicked pane), since the close target is that single cell, not the whole column. Set `false` to hide the glyph for keyboard-driven users.
>
> **`close_button_color` — close glyph color.** The color of the `close_button` glyph (default `theme`). `theme` uses zellij's own alert red — your theme's `exit_code_error` color, falling back to a built-in red when the theme leaves it unset. Some themes derive that alert color from a near-black or near-white `red` (e.g. sobrio's `red "#121212"`), which renders the `theme` glyph almost invisible; override it with `fg` (the active label's white — always legible on a colored tab), `red` (the plugin's built-in alert red, independent of the theme), or any `#rrggbb` hex (e.g. `"#ff5555"`). Affects only the close glyph and is ignored when `close_button "false"`.
Expand Down
2 changes: 0 additions & 2 deletions cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ If you pin to a version URL, paste this block into your zellij `permissions.kdl`
}
```

If `reorder "true"` is set, also add `RunActionsAsUser` to that block.

> Using `releases/latest/download/` in your layout? The permission is keyed to that URL and was granted once — no action needed.
"""
trim = true
Expand Down
107 changes: 1 addition & 106 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use std::collections::BTreeMap;
use crate::color::Rgb;
use crate::line::Alignment;
use crate::minimap::{GradientMode, GradientShape, GradientSpec, RadialDirection};
use crate::scroll::ScrollMode;

/// Parsed plugin configuration.
#[derive(Clone, Debug, PartialEq, Eq)]
Expand All @@ -35,12 +34,6 @@ pub struct Config {
pub tab_gap: usize,
/// Whether to draw a 1px dark separator between adjacent panes.
pub gutter: bool,
/// Whether drag-to-reorder is enabled. Off by default: the plugin then
/// requests only the v0.1.0 permission set (`ReadApplicationState` +
/// `ChangeApplicationState`), so existing users do not hit a
/// `RunActionsAsUser` cache miss on auto-update (zellij#4982). On → the
/// third permission is requested and a tab drag reorders.
pub reorder: bool,
/// Gradient sweep applied to each pane block's fill. Defaults to `sheen`
/// — the polished out-of-the-box look; `off` restores the flat
/// v0.1.0-style fills. See [`GradientMode`].
Expand Down Expand Up @@ -75,12 +68,6 @@ pub struct Config {
/// new permission prompt appears on auto-update. `false` hides the button and
/// reclaims its columns for the tab strip.
pub new_tab_button: bool,
/// How the mouse wheel navigates over the bar (#80). `tab` (default) switches
/// tabs, `pane` walks the focused pane across tab boundaries, `off` makes the
/// wheel inert. zellij delivers scroll events without a position, so the
/// gesture acts on the whole bar. Needs no permission beyond the default set
/// (`ChangeApplicationState`). See [`ScrollMode`].
pub scroll: ScrollMode,
/// Whether each tab block draws a clickable close button near its top-right
/// corner, closing that tab on click (#86). On by default (#94): the close
/// glyph rides on the already-granted `ChangeApplicationState` permission
Expand All @@ -102,17 +89,6 @@ pub struct Config {
/// red independent of the theme), or a `#rrggbb` hex to override it. See
/// [`CloseColor`].
pub close_button_color: CloseColor,
/// Cooldown window in **milliseconds** between wheel navigation steps (#83). A
/// stepless device (Magic Mouse, trackpad) fires a *burst* of scroll events for
/// one flick, so the pre-#83 "one event = one step" raced through tabs/panes.
/// This is a leading-edge rate limiter: the first event navigates at once and
/// opens the window, and events arriving within `scroll_cooldown_ms` of it are
/// dropped — so a flick advances about one step per window while a deliberate,
/// well-spaced notch always steps immediately. `40` (default) suits both a
/// notched wheel and a trackpad; raise it to damp the wheel further. `0`
/// disables the limiter (every event steps, the pre-#83 feel). Ignored when
/// `scroll` is `off`. See [`crate::scroll::gate`].
pub scroll_cooldown_ms: usize,
}

impl Config {
Expand All @@ -124,16 +100,14 @@ impl Config {
pub const DEFAULT_ACTIVE_WIDTH: usize = 24;
/// Default alignment — centered, preserving the v0.1.0 sliding behavior so
/// existing layouts render identically on auto-update (opt into `left` to
/// anchor the row). Same default-preserve rationale as [`Self::DEFAULT_REORDER`].
/// anchor the row).
pub const DEFAULT_ALIGN: Alignment = Alignment::Center;
/// Default gap between tab blocks — `2` cleared columns, so adjacent
/// screens read as separate blocks out of the box. Set `0` to pack the
/// blocks flush (the original v0.1.0 look).
pub const DEFAULT_TAB_GAP: usize = 2;
/// Default gutter state — no separator.
pub const DEFAULT_GUTTER: bool = false;
/// Default reorder state — off, preserving the v0.1.0 permission posture.
pub const DEFAULT_REORDER: bool = false;
/// Default gradient mode — `Sheen`, the polished out-of-the-box look.
/// Set `off` to restore the flat v0.1.0-style fills.
pub const DEFAULT_GRADIENT: GradientMode = GradientMode::Sheen;
Expand All @@ -156,9 +130,6 @@ impl Config {
/// box (#76). It rides on the already-granted `ChangeApplicationState`
/// permission, so enabling it by default costs existing users no new prompt.
pub const DEFAULT_NEW_TAB_BUTTON: bool = true;
/// Default wheel behaviour — `Tab`, matching zellij's stock tab-bar (scroll
/// switches tabs). Set `pane` to walk panes, `off` to disable (#80).
pub const DEFAULT_SCROLL: ScrollMode = ScrollMode::Tab;
/// Default close-button state — on (#94), so the close affordance is present
/// out of the box. It rides on the already-granted `ChangeApplicationState`
/// permission (#86), so enabling it by default costs existing users no new
Expand All @@ -169,10 +140,6 @@ impl Config {
/// `red`, or a `#rrggbb` hex when a theme's dark error color makes the glyph
/// hard to read (#94 follow-up).
pub const DEFAULT_CLOSE_BUTTON_COLOR: CloseColor = CloseColor::Theme;
/// Default wheel cooldown — `40` ms between steps, taming a stepless device's
/// flick burst out of the box while still letting a deliberate notch step at
/// once. Set `0` to disable the limiter (the pre-#83 one-step-per-event feel) (#83).
pub const DEFAULT_SCROLL_COOLDOWN_MS: usize = 40;

/// Parse the configuration map, falling back to a default for any missing or
/// malformed value. Total: never panics on bad input.
Expand All @@ -198,10 +165,6 @@ impl Config {
.get("gutter")
.and_then(|raw| raw.parse().ok())
.unwrap_or(Self::DEFAULT_GUTTER),
reorder: configuration
.get("reorder")
.and_then(|raw| raw.parse().ok())
.unwrap_or(Self::DEFAULT_REORDER),
gradient: configuration
.get("gradient")
.and_then(|raw| raw.parse().ok())
Expand Down Expand Up @@ -231,10 +194,6 @@ impl Config {
.get("new_tab_button")
.and_then(|raw| raw.parse().ok())
.unwrap_or(Self::DEFAULT_NEW_TAB_BUTTON),
scroll: configuration
.get("scroll")
.and_then(|raw| raw.parse().ok())
.unwrap_or(Self::DEFAULT_SCROLL),
close_button: configuration
.get("close_button")
.and_then(|raw| raw.parse().ok())
Expand All @@ -243,10 +202,6 @@ impl Config {
.get("close_button_color")
.and_then(|raw| raw.parse().ok())
.unwrap_or(Self::DEFAULT_CLOSE_BUTTON_COLOR),
scroll_cooldown_ms: configuration
.get("scroll_cooldown_ms")
.and_then(|raw| raw.parse().ok())
.unwrap_or(Self::DEFAULT_SCROLL_COOLDOWN_MS),
}
}

Expand Down Expand Up @@ -355,17 +310,14 @@ mod tests {
assert_eq!(config.align, Alignment::Center);
assert_eq!(config.tab_gap, 2);
assert!(!config.gutter);
assert!(!config.reorder);
assert_eq!(config.gradient, GradientMode::Sheen);
assert_eq!(config.gradient_shape, GradientShape::Linear);
assert_eq!(config.gradient_angle, 0);
assert_eq!(config.gradient_radial, RadialDirection::Outward);
assert!(config.inactive_dim);
assert!(config.perspective);
assert!(config.new_tab_button);
assert_eq!(config.scroll, ScrollMode::Tab);
assert!(config.close_button);
assert_eq!(config.scroll_cooldown_ms, 40);
}

#[test]
Expand All @@ -376,15 +328,13 @@ mod tests {
("align", "left"),
("tab_gap", "1"),
("gutter", "true"),
("reorder", "true"),
("gradient", "sheen"),
]);
assert_eq!(config.shortcut_prefix, "C-");
assert_eq!(config.active_width, 30);
assert_eq!(config.align, Alignment::Left);
assert_eq!(config.tab_gap, 1);
assert!(config.gutter);
assert!(config.reorder);
assert_eq!(config.gradient, GradientMode::Sheen);
}

Expand Down Expand Up @@ -523,12 +473,6 @@ mod tests {
assert!(!config_from(&[("gutter", "maybe")]).gutter);
}

#[test]
fn malformed_reorder_falls_back() {
assert!(!config_from(&[("reorder", "yes")]).reorder);
assert!(!config_from(&[("reorder", "")]).reorder);
}

#[test]
fn malformed_tab_gap_falls_back() {
assert_eq!(config_from(&[("tab_gap", "wide")]).tab_gap, 2);
Expand Down Expand Up @@ -559,21 +503,6 @@ mod tests {
assert!(!config_from(&[("perspective", "false")]).perspective);
}

#[test]
fn parses_scroll_modes() {
assert_eq!(config_from(&[("scroll", "tab")]).scroll, ScrollMode::Tab);
assert_eq!(config_from(&[("scroll", "pane")]).scroll, ScrollMode::Pane);
assert_eq!(config_from(&[("scroll", "off")]).scroll, ScrollMode::Off);
}

#[test]
fn malformed_scroll_falls_back_to_tab() {
// Unknown / wrong-case / empty values keep the zellij-stock default.
assert_eq!(config_from(&[("scroll", "wheel")]).scroll, ScrollMode::Tab);
assert_eq!(config_from(&[("scroll", "Tab")]).scroll, ScrollMode::Tab);
assert_eq!(config_from(&[("scroll", "")]).scroll, ScrollMode::Tab);
}

#[test]
fn parses_explicit_close_button_off() {
// The close glyph is on by default (#94); an explicit `false` opts out.
Expand Down Expand Up @@ -654,39 +583,6 @@ mod tests {
);
}

#[test]
fn parses_scroll_cooldown_ms() {
// A valid millisecond window is preserved verbatim; `0` is the documented
// value for disabling the limiter (the pre-#83 one-step-per-event feel).
assert_eq!(
config_from(&[("scroll_cooldown_ms", "80")]).scroll_cooldown_ms,
80
);
assert_eq!(
config_from(&[("scroll_cooldown_ms", "0")]).scroll_cooldown_ms,
0
);
}

#[test]
fn malformed_scroll_cooldown_ms_falls_back() {
// Non-numeric / negative / empty values keep the taming default. An
// explicit `0` parses fine here (it disables the limiter at the use site,
// where `gate` short-circuits to `Navigate`).
assert_eq!(
config_from(&[("scroll_cooldown_ms", "fast")]).scroll_cooldown_ms,
40
);
assert_eq!(
config_from(&[("scroll_cooldown_ms", "-1")]).scroll_cooldown_ms,
40
);
assert_eq!(
config_from(&[("scroll_cooldown_ms", "")]).scroll_cooldown_ms,
40
);
}

#[test]
fn malformed_perspective_falls_back() {
// A malformed or empty value keeps the on-by-default cue.
Expand Down Expand Up @@ -715,6 +611,5 @@ mod tests {
assert_eq!(config.align, Alignment::Center);
assert_eq!(config.tab_gap, 2);
assert!(!config.gutter);
assert!(!config.reorder);
}
}
Loading
Loading