aboutsummaryrefslogtreecommitdiffstats
path: root/zellij-server/src
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-08 21:35:32 +0200
committeruser <user@clank>2026-08-08 21:35:32 +0200
commit4cc7c39fee932b75a5a1e3dce85b6bf3efe6c5d3 (patch)
treea08d1bdf09e224b1789430c5980366c452acb676 /zellij-server/src
parentsl0p: tag fork builds as 0.45.0+sl0p1 (diff)
downloadsl0ppij-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.
Diffstat (limited to 'zellij-server/src')
-rw-r--r--zellij-server/src/panes/plugin_pane.rs27
-rw-r--r--zellij-server/src/panes/terminal_pane.rs25
2 files changed, 40 insertions, 12 deletions
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,