Skip to content
Open
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Unreleased]

* fix(compact-bar): wheel events over the one-row chrome target the exact focused terminal pane at its cursor (with a content-center fallback) instead of switching tabs, preserving native mouse-aware TUI scrolling, alternate-screen fallback and ordinary scrollback without coordinate hit-test races; the compact bar clamps large trackpad deltas to the shared 100-line limit, the plugin command requires stdin permission and rejects larger third-party requests, and the host revalidates stale cursor positions against the pane's current dimensions after resize
* fix(chrome): Composer and Quick cmd remain clickable, label-only actions in the top bar while their `⌘E` / `⇧⌘.` teaching lane stays permanently in the bottom bar across input modes; the theme glyph keeps a one-column trailing inset in borderless hosts
* fix(server): a starting server no longer unlinks and re-binds a session socket that a live server still owns — it probes the path first and refuses to start when someone is listening, even if that server is too busy to answer a health probe; only missing, stale or non-socket paths are cleaned up. A server already running a session likewise rejects a second new-session request instead of re-initializing over live state
* fix(chrome): the chrome a client had visible is parked when that client detaches, so a server with nobody attached stops refreshing the cross-session list once a second; attaching re-activates it and rebuilds the list on the spot
Expand Down
145 changes: 134 additions & 11 deletions default-plugins/compact-bar/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ mod line;
mod tab;
mod tooltip;

use std::cmp::{max, min};
use std::collections::{BTreeMap, BTreeSet};
use std::convert::TryInto;
use std::path::PathBuf;
Expand Down Expand Up @@ -444,8 +443,8 @@ impl State {

match mouse_event {
Mouse::LeftClick(_, col) => self.handle_tab_click(col),
Mouse::ScrollUp(_) => self.scroll_tab_up(),
Mouse::ScrollDown(_) => self.scroll_tab_down(),
Mouse::ScrollUp(lines) => self.forward_scroll_to_focused_pane(true, lines),
Mouse::ScrollDown(lines) => self.forward_scroll_to_focused_pane(false, lines),
Comment thread
m-szymanska marked this conversation as resolved.
_ => {},
}
}
Expand Down Expand Up @@ -635,10 +634,72 @@ impl State {
changed
}

fn scroll_tab_up(&self) {
let next_tab = min(self.active_tab_idx + 1, self.tabs.len());
switch_tab_to(next_tab as u32);
fn forward_scroll_to_focused_pane(&self, scroll_up: bool, lines: usize) {
let Ok((_, focused_pane_id)) = get_focused_pane_info() else {
return;
};
let Some(focused_pane) = get_pane_info(focused_pane_id) else {
return;
};
let Some((pane_id, position)) =
focused_terminal_scroll_target(focused_pane_id, &focused_pane)
else {
return;
};
let lines = bounded_mouse_scroll_lines(lines);
if scroll_up {
mouse_scroll_up_in_pane_id(pane_id, position, lines);
} else {
mouse_scroll_down_in_pane_id(pane_id, position, lines);
}
}
}

fn bounded_mouse_scroll_lines(lines: usize) -> usize {
lines.min(plugin_api::plugin_command::MAX_MOUSE_SCROLL_LINES_IN_PANE_ID)
}

fn focused_terminal_scroll_target(
focused_pane_id: PaneId,
focused_pane: &PaneInfo,
) -> Option<(PaneId, Position)> {
let pane_id = if focused_pane.is_plugin {
PaneId::Plugin(focused_pane.id)
} else {
PaneId::Terminal(focused_pane.id)
};
if focused_pane.is_plugin || pane_id != focused_pane_id {
return None;
}
if focused_pane.pane_content_rows == 0 || focused_pane.pane_content_columns == 0 {
return None;
}

let content_offset_column = focused_pane
.pane_content_x
.saturating_sub(focused_pane.pane_x);
let content_offset_line = focused_pane
.pane_content_y
.saturating_sub(focused_pane.pane_y);
let (column, line) = focused_pane
.cursor_coordinates_in_pane
.and_then(|(column, line)| {
Some((
column.checked_sub(content_offset_column)?,
line.checked_sub(content_offset_line)?,
))
})
.filter(|(column, line)| {
*column < focused_pane.pane_content_columns && *line < focused_pane.pane_content_rows
})
.unwrap_or((
focused_pane.pane_content_columns / 2,
focused_pane.pane_content_rows / 2,
));
Some((
focused_pane_id,
Position::new(line.try_into().ok()?, column.try_into().ok()?),
))
}

/// Quick cmd mini console: shallow, wide, upper-center — non-ephemeral
Expand Down Expand Up @@ -704,11 +765,6 @@ fn open_composer() {
}

impl State {
fn scroll_tab_down(&self) {
let prev_tab = max(self.active_tab_idx.saturating_sub(1), 1);
switch_tab_to(prev_tab as u32);
}

fn clear_clipboard_state(&mut self) {
self.text_copy_destination = None;
self.display_system_clipboard_failure = false;
Expand Down Expand Up @@ -971,6 +1027,73 @@ mod transient_dimension_guard_tests {
assert!(!state.quick_cmd_message_targets_active_bar(&public_message));
}

#[test]
fn wheel_actions_target_the_focused_terminal_cursor() {
let focused_terminal = PaneInfo {
is_focused: true,
pane_x: 23,
pane_y: 1,
pane_content_x: 24,
pane_content_y: 2,
pane_content_columns: 80,
pane_content_rows: 20,
cursor_coordinates_in_pane: Some((8, 4)),
..Default::default()
};
let target =
focused_terminal_scroll_target(PaneId::Terminal(0), &focused_terminal).unwrap();
assert_eq!(target, (PaneId::Terminal(0), Position::new(3, 7)));
}

#[test]
fn wheel_actions_fall_back_to_the_content_center() {
let focused_terminal = PaneInfo {
id: 4,
pane_content_x: 24,
pane_content_y: 2,
pane_content_columns: 80,
pane_content_rows: 20,
..Default::default()
};

let target =
focused_terminal_scroll_target(PaneId::Terminal(4), &focused_terminal).unwrap();
assert_eq!(target, (PaneId::Terminal(4), Position::new(10, 40)));
}

#[test]
fn wheel_forwarding_bounds_large_trackpad_deltas() {
assert_eq!(bounded_mouse_scroll_lines(3), 3);
assert_eq!(bounded_mouse_scroll_lines(100), 100);
assert_eq!(bounded_mouse_scroll_lines(usize::MAX), 100);
}

#[test]
fn wheel_forwarding_ignores_plugin_only_and_empty_content_surfaces() {
let plugin_only = PaneInfo {
is_focused: true,
is_plugin: true,
pane_content_columns: 80,
pane_content_rows: 20,
..Default::default()
};
assert_eq!(
focused_terminal_scroll_target(PaneId::Plugin(0), &plugin_only),
None
);

let empty_terminal = PaneInfo {
is_focused: true,
pane_content_columns: 0,
pane_content_rows: 20,
..Default::default()
};
assert_eq!(
focused_terminal_scroll_target(PaneId::Terminal(0), &empty_terminal),
None
);
}

#[test]
fn theme_command_result_tracks_dark_and_light_without_accepting_noise() {
let mut state = State::default();
Expand Down
38 changes: 37 additions & 1 deletion zellij-server/src/plugins/zellij_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use zellij_utils::data::{
use zellij_utils::home::default_layout_dir;
use zellij_utils::input::permission::PermissionCache;
use zellij_utils::ipc::{ClientToServerMsg, IpcSenderWithContext};
use zellij_utils::position::Position;
use zellij_utils::sessions::generate_random_name as generate_random_name_impl;
#[cfg(feature = "web_server_capability")]
use zellij_utils::web_authentication_tokens::{
Expand Down Expand Up @@ -517,6 +518,12 @@ fn host_run_plugin_command(mut caller: Caller<'_, PluginEnv>) {
PluginCommand::ScrollDownInPaneId(pane_id) => {
scroll_down_in_pane_id(env, pane_id.into())
},
PluginCommand::MouseScrollUpInPaneId(pane_id, position, lines) => {
mouse_scroll_up_in_pane_id(env, pane_id.into(), position, lines)
},
PluginCommand::MouseScrollDownInPaneId(pane_id, position, lines) => {
mouse_scroll_down_in_pane_id(env, pane_id.into(), position, lines)
},
PluginCommand::ScrollToTopInPaneId(pane_id) => {
scroll_to_top_in_pane_id(env, pane_id.into())
},
Expand Down Expand Up @@ -4689,6 +4696,33 @@ fn scroll_down_in_pane_id(env: &PluginEnv, pane_id: PaneId) {
.send_to_screen(ScreenInstruction::ScrollDownInPaneId(pane_id));
}

fn mouse_scroll_up_in_pane_id(env: &PluginEnv, pane_id: PaneId, position: Position, lines: usize) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::MouseScrollUpInPaneId(
pane_id,
position,
lines,
env.client_id,
));
}

fn mouse_scroll_down_in_pane_id(
env: &PluginEnv,
pane_id: PaneId,
position: Position,
lines: usize,
) {
let _ = env
.senders
.send_to_screen(ScreenInstruction::MouseScrollDownInPaneId(
pane_id,
position,
lines,
env.client_id,
));
}

fn scroll_to_top_in_pane_id(env: &PluginEnv, pane_id: PaneId) {
let _ = env
.senders
Expand Down Expand Up @@ -5355,7 +5389,9 @@ fn check_command_permission(
PluginCommand::Write(..)
| PluginCommand::WriteChars(..)
| PluginCommand::WriteToPaneId(..)
| PluginCommand::WriteCharsToPaneId(..) => PermissionType::WriteToStdin,
| PluginCommand::WriteCharsToPaneId(..)
| PluginCommand::MouseScrollUpInPaneId(..)
| PluginCommand::MouseScrollDownInPaneId(..) => PermissionType::WriteToStdin,
PluginCommand::CopyToClipboard(..) => PermissionType::WriteToClipboard,
PluginCommand::SwitchTabTo(..)
| PluginCommand::SwitchToMode(..)
Expand Down
26 changes: 26 additions & 0 deletions zellij-server/src/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,8 @@ pub enum ScreenInstruction {
ClearScreenForPaneId(PaneId),
ScrollUpInPaneId(PaneId),
ScrollDownInPaneId(PaneId),
MouseScrollUpInPaneId(PaneId, Position, usize, ClientId),
MouseScrollDownInPaneId(PaneId, Position, usize, ClientId),
ScrollToTopInPaneId(PaneId),
ScrollToBottomInPaneId(PaneId),
PageScrollUpInPaneId(PaneId),
Expand Down Expand Up @@ -1329,6 +1331,10 @@ impl From<&ScreenInstruction> for ScreenContext {
ScreenInstruction::ClearScreenForPaneId(..) => ScreenContext::ClearScreenForPaneId,
ScreenInstruction::ScrollUpInPaneId(..) => ScreenContext::ScrollUpInPaneId,
ScreenInstruction::ScrollDownInPaneId(..) => ScreenContext::ScrollDownInPaneId,
ScreenInstruction::MouseScrollUpInPaneId(..) => ScreenContext::MouseScrollUpInPaneId,
ScreenInstruction::MouseScrollDownInPaneId(..) => {
ScreenContext::MouseScrollDownInPaneId
},
ScreenInstruction::ScrollToTopInPaneId(..) => ScreenContext::ScrollToTopInPaneId,
ScreenInstruction::ScrollToBottomInPaneId(..) => ScreenContext::ScrollToBottomInPaneId,
ScreenInstruction::PageScrollUpInPaneId(..) => ScreenContext::PageScrollUpInPaneId,
Expand Down Expand Up @@ -15000,6 +15006,26 @@ pub(crate) fn screen_thread_main(params: ScreenThreadParams) -> Result<()> {
}
screen.render(None)?;
},
ScreenInstruction::MouseScrollUpInPaneId(pane_id, position, lines, client_id) => {
let all_tabs = screen.get_tabs_mut();
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
tab.handle_scrollwheel_up_in_pane(pane_id, &position, lines, client_id)?;
break;
}
}
screen.render(None)?;
},
ScreenInstruction::MouseScrollDownInPaneId(pane_id, position, lines, client_id) => {
let all_tabs = screen.get_tabs_mut();
for tab in all_tabs.values_mut() {
if tab.has_pane_with_pid(&pane_id) {
tab.handle_scrollwheel_down_in_pane(pane_id, &position, lines, client_id)?;
break;
}
}
screen.render(None)?;
},
ScreenInstruction::ScrollToTopInPaneId(pane_id) => {
let all_tabs = screen.get_tabs_mut();
for tab in all_tabs.values_mut() {
Expand Down
32 changes: 32 additions & 0 deletions zellij-server/src/tab/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5795,6 +5795,22 @@ impl Tab {
MouseHandler::handle_scrollwheel_up(self, point, lines, client_id)
}

pub fn handle_scrollwheel_up_in_pane(
&mut self,
pane_id: PaneId,
relative_position: &Position,
lines: usize,
client_id: ClientId,
) -> Result<()> {
MouseHandler::handle_scrollwheel_up_in_pane(
self,
pane_id,
relative_position,
lines,
client_id,
)
}

pub fn handle_scrollwheel_down(
&mut self,
point: &Position,
Expand All @@ -5804,6 +5820,22 @@ impl Tab {
MouseHandler::handle_scrollwheel_down(self, point, lines, client_id)
}

pub fn handle_scrollwheel_down_in_pane(
&mut self,
pane_id: PaneId,
relative_position: &Position,
lines: usize,
client_id: ClientId,
) -> Result<()> {
MouseHandler::handle_scrollwheel_down_in_pane(
self,
pane_id,
relative_position,
lines,
client_id,
)
}

fn get_pane_id_at(
&mut self,
point: &Position,
Expand Down
Loading
Loading