diff options
| author | user <user@clank> | 2026-08-08 21:35:32 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-08 21:35:32 +0200 |
| commit | 4cc7c39fee932b75a5a1e3dce85b6bf3efe6c5d3 (patch) | |
| tree | a08d1bdf09e224b1789430c5980366c452acb676 | |
| parent | sl0p: tag fork builds as 0.45.0+sl0p1 (diff) | |
| download | sl0ppij-4cc7c39fee932b75a5a1e3dce85b6bf3efe6c5d3.tar.gz sl0ppij-4cc7c39fee932b75a5a1e3dce85b6bf3efe6c5d3.tar.xz sl0ppij-4cc7c39fee932b75a5a1e3dce85b6bf3efe6c5d3.zip | |
sl0p: ignore gap/padding when a pane draws no frame
Closes the frameless-mode hole documented in the README.
In frameless mode (pane_frame_style "titles" or "none") the cells around a
pane's content are owned by the inter-pane boundary renderer, not the pane.
The ghost-fix commit correctly stopped panes from BLANKING that region -
blanking it erased the boundaries and broke three tests - but nothing stopped
them from CREATING it: padding still inset the content, so the vacated cells
had no owner and kept whatever was drawn there before.
Half the job done in each direction produced exactly the ghosting the blanking
was meant to prevent, just in a mode we do not use by default.
Fix: a pane that is not drawing a frame is not inset at all - no gap, no
padding, no moat, nothing unowned. Pane::set_frame() already received the
right boolean from both the tiled and floating containers and was throwing it
away; it now records it.
Config values are preserved, they simply take effect only with full frames.
README updated to describe this as a defined limit rather than a known bug.
Baselines held: zellij-server 1421/30, zellij-utils 435/5.
| -rw-r--r-- | README.md | 13 | ||||
| -rw-r--r-- | zellij-server/src/panes/plugin_pane.rs | 27 | ||||
| -rw-r--r-- | zellij-server/src/panes/terminal_pane.rs | 25 |
3 files changed, 47 insertions, 18 deletions
@@ -76,12 +76,13 @@ the fork — check against `upstream/main` before chasing one). ## Known rough edges -- **Frameless mode is not supported.** `gap` and `padding` are designed for - `pane_frame_style "full"`. With `"titles"` or `"none"`, the cells around a pane's - content belong to the inter-pane boundary renderer (`ui/boundaries.rs`), which knows - nothing about these insets. `padding` still shrinks the content in that mode but the - vacated cells are not repainted, so expect stale glyphs when resizing. Use `gap 0` - and `padding 0` with frameless pane frames until this is addressed. +- **`gap` and `padding` only apply with `pane_frame_style "full"`.** With `"titles"` or + `"none"` they are ignored: the cells around a pane's content there belong to the + inter-pane boundary renderer (`ui/boundaries.rs`), and insetting into that region + would leave cells nobody repaints (stale glyphs on resize) or paint over the + boundaries themselves. Rather than half-apply, a frameless pane is simply not inset. + Your config keeps its values; they just take effect if you switch back to full + frames. - **Floating panes** get `padding` but no `gap`: they are positioned explicitly, so an inter-pane gap does not apply. - **Stacked panes** apply the gap uniformly; stack header spacing may look slightly off diff --git a/zellij-server/src/panes/plugin_pane.rs b/zellij-server/src/panes/plugin_pane.rs index 76a94662..1d77f1b0 100644 --- a/zellij-server/src/panes/plugin_pane.rs +++ b/zellij-server/src/panes/plugin_pane.rs @@ -86,6 +86,8 @@ pub(crate) struct PluginPane { /// a side only gets a gap where one belongs, so two neighbours produce ONE /// gap between them rather than two. gap_offset: Offset, + /// Whether this pane currently draws a frame. See set_frame(). + draws_frame: bool, pub send_plugin_instructions: SenderWithContext<PluginInstruction>, pub active_at: Instant, pub pane_title: String, @@ -149,6 +151,7 @@ impl PluginPane { frame: HashMap::new(), content_offset: Offset::default(), gap_offset: Offset::default(), + draws_frame: true, pane_title: title, borderless: false, pane_name: pane_name.clone(), @@ -480,7 +483,11 @@ impl Pane for PluginPane { // see the matching comment in terminal_pane.rs: the frame is drawn // inset from the pane's allocated geometry to produce the outer gap let outer_frame_viewport = Viewport::from(frame_geom); - let frame_viewport = outer_frame_viewport.inset_by(self.gap_offset); + let frame_viewport = outer_frame_viewport.inset_by(if self.draws_frame { + self.gap_offset + } else { + Offset::default() + }); // Every cell of the pane's allocation must be written by exactly one // pane each render, or stale glyphs survive as ghosting. The gap and // padding insets carve out TWO regions that neither the frame nor the @@ -756,18 +763,24 @@ impl Pane for PluginPane { fn set_active_at(&mut self, time: Instant) { self.active_at = time; } - fn set_frame(&mut self, _frame: bool) { + fn set_frame(&mut self, frame: bool) { + // Recorded, not just discarded: gap/padding must be disabled when this + // pane is not drawing a frame. In frameless mode the cells around the + // content belong to the inter-pane boundary renderer, and a pane that + // insets its content there leaves cells that nobody repaints. + self.draws_frame = frame; self.frame.clear(); } fn set_gap_offset(&mut self, gap_offset: Offset) { self.gap_offset = gap_offset; } fn set_content_offset(&mut self, offset: Offset) { - // see terminal_pane.rs: borderless panes are chrome and are sized exactly - // to their content, so insetting them at all makes them vanish. This is - // the impl that matters in practice - zjstatus/status-bar/tab-bar are - // all plugin panes. - let inset = if self.borderless { + let inset = if self.borderless || !self.draws_frame { + // borderless panes are chrome, sized exactly to their content, so + // any inset makes them vanish. Frameless panes must not be inset + // either: the cells around their content are owned by the + // inter-pane boundary renderer, so a moat we create here is a moat + // nobody repaints -> stale glyphs on resize. Offset::default() } else { Offset { diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index 0e2188f2..804291df 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -144,6 +144,8 @@ pub struct TerminalPane { /// viewport: a side only gets a gap where one belongs, so two /// neighbours produce ONE gap between them rather than two. gap_offset: Offset, + /// Whether this pane currently draws a frame. See set_frame(). + draws_frame: bool, pane_title: String, pane_name: String, prev_pane_name: String, @@ -584,7 +586,11 @@ impl Pane for TerminalPane { // cells between neighbouring panes. Content is inset to match in // set_content_offset(). let outer_frame_viewport = Viewport::from(frame_geom); - let frame_viewport = outer_frame_viewport.inset_by(self.gap_offset); + let frame_viewport = outer_frame_viewport.inset_by(if self.draws_frame { + self.gap_offset + } else { + Offset::default() + }); // Every cell of the pane's allocation must be written by exactly one // pane each render, or stale glyphs survive as ghosting. The gap and // padding insets carve out TWO regions that neither the frame nor the @@ -973,7 +979,12 @@ impl Pane for TerminalPane { self.grid.get_selected_text() } - fn set_frame(&mut self, _frame: bool) { + fn set_frame(&mut self, frame: bool) { + // Recorded, not just discarded: gap/padding must be disabled when this + // pane is not drawing a frame. In frameless mode the cells around the + // content belong to the inter-pane boundary renderer, and a pane that + // insets its content there leaves cells that nobody repaints. + self.draws_frame = frame; self.frame.clear(); } @@ -981,9 +992,12 @@ impl Pane for TerminalPane { self.gap_offset = gap_offset; } fn set_content_offset(&mut self, offset: Offset) { - // borderless panes are UI chrome (status bar, tab bar): they are sized - // exactly to their content, so insetting them at all makes them vanish - let inset = if self.borderless { + let inset = if self.borderless || !self.draws_frame { + // borderless panes are chrome, sized exactly to their content, so + // any inset makes them vanish. Frameless panes must not be inset + // either: the cells around their content are owned by the + // inter-pane boundary renderer, so a moat we create here is a moat + // nobody repaints -> stale glyphs on resize. Offset::default() } else { Offset { @@ -1447,6 +1461,7 @@ impl TerminalPane { frame: HashMap::new(), content_offset: Offset::default(), gap_offset: Offset::default(), + draws_frame: true, pid, grid, selectable: true, |
