diff options
| author | Aram Drevekenin <aram@poor.dev> | 2026-03-06 15:23:17 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-06 15:23:17 +0100 |
| commit | 85d29a1e660ff2d22900391587bba7639c159c8b (patch) | |
| tree | bf2da6b2c2d38ed62139abdf2924803ace9cd8c7 | |
| parent | Click file paths to open in editor, allow plugins to change styles and hover ... (diff) | |
| download | sl0ppij-85d29a1e660ff2d22900391587bba7639c159c8b.tar.gz sl0ppij-85d29a1e660ff2d22900391587bba7639c159c8b.tar.xz sl0ppij-85d29a1e660ff2d22900391587bba7639c159c8b.zip | |
fix: click links with alt-click (#4809)
30 files changed, 368 insertions, 23 deletions
diff --git a/default-plugins/fixture-plugin-for-tests/src/main.rs b/default-plugins/fixture-plugin-for-tests/src/main.rs index da0e6482..96ac3fa3 100644 --- a/default-plugins/fixture-plugin-for-tests/src/main.rs +++ b/default-plugins/fixture-plugin-for-tests/src/main.rs @@ -601,6 +601,7 @@ impl ZellijPlugin for State { bold: false, italic: true, underline: true, + tooltip_text: None, }]; set_pane_regex_highlights(PaneId::Terminal(1), highlights); }, diff --git a/default-plugins/link/src/main.rs b/default-plugins/link/src/main.rs index 3862e5d0..297c2acf 100644 --- a/default-plugins/link/src/main.rs +++ b/default-plugins/link/src/main.rs @@ -180,6 +180,7 @@ impl State { bold: false, italic: true, underline: true, + tooltip_text: Some("Open".to_string()), }); // Directory-entry patterns for the pane's current CWD @@ -194,6 +195,7 @@ impl State { bold: false, italic: true, underline: true, + tooltip_text: Some("Open".to_string()), }); } } diff --git a/zellij-server/src/panes/grid.rs b/zellij-server/src/panes/grid.rs index cb629ac9..b6b64abf 100644 --- a/zellij-server/src/panes/grid.rs +++ b/zellij-server/src/panes/grid.rs @@ -533,6 +533,7 @@ pub struct Grid { pub plugin_highlights: HashMap<u32, Vec<(String, CompiledHighlight)>>, // key: plugin_id (u32), inner vec: (pattern, compiled) pairs pub hover_position: Option<Position>, // pane-relative cursor cell; None when outside pane + pub cached_hover_tooltip: Option<String>, } impl Grid { @@ -569,6 +570,7 @@ pub struct CompiledHighlight { pub bold: bool, pub italic: bool, pub underline: bool, + pub tooltip_text: Option<String>, } impl CompiledHighlight { @@ -813,6 +815,7 @@ impl Grid { pane_default_bg: None, plugin_highlights: HashMap::new(), hover_position: None, + cached_hover_tooltip: None, } } pub fn render_full_viewport(&mut self) { @@ -2095,6 +2098,7 @@ impl Grid { bold: h.bold, italic: h.italic, underline: h.underline, + tooltip_text: h.tooltip_text.clone(), }; } else { slot.push(( @@ -2108,6 +2112,7 @@ impl Grid { bold: h.bold, italic: h.italic, underline: h.underline, + tooltip_text: h.tooltip_text, }, )); } @@ -2120,12 +2125,14 @@ impl Grid { } } self.output_buffer.update_all_lines(); + self.recompute_hover_tooltip(); } /// Called by the server-side handler for ClearPaneHighlights. pub fn clear_plugin_highlights(&mut self, plugin_id: u32) { if self.plugin_highlights.remove(&plugin_id).is_some() { self.output_buffer.update_all_lines(); + self.recompute_hover_tooltip(); } } @@ -2180,6 +2187,7 @@ impl Grid { } self.hover_position = new_pos; + self.recompute_hover_tooltip(); } /// Mark all physical rows belonging to the logical line group that contains @@ -2194,6 +2202,49 @@ impl Grid { } } + /// Recompute the cached hover tooltip from the current hover position and + /// plugin highlights. Called whenever the hover position, highlight set, or + /// highlight clearing changes. + fn recompute_hover_tooltip(&mut self) { + self.cached_hover_tooltip = None; + let hover_pos = match self.hover_position { + Some(p) => p, + None => return, + }; + if self.mouse_tracking != MouseTracking::Off { + return; + } + if self.plugin_highlights.is_empty() { + return; + } + let hover_row = hover_pos.line.0 as usize; + let hover_col = hover_pos.column.0 as usize; + let (_canonical, _group_len, logical_text, boundaries) = + match collect_and_build_logical_line(&self.viewport, hover_row) { + Some(v) => v, + None => return, + }; + for (_plugin_id, pattern_map) in &self.plugin_highlights { + for (_pattern, compiled) in pattern_map { + if !compiled.on_hover || compiled.tooltip_text.is_none() { + continue; + } + for mat in compiled.regex.find_iter(&logical_text) { + if let Some((_sel, start_row, start_col, end_row, end_col)) = + match_to_selection(&mat, &boundaries, &self.viewport) + { + if position_in_span( + hover_row, hover_col, start_row, start_col, end_row, end_col, + ) { + self.cached_hover_tooltip = compiled.tooltip_text.clone(); + return; + } + } + } + } + } + } + /// Pre-compute plugin highlight selections across all logical line groups in /// the viewport. Hover highlights are emitted first so they take priority /// when the cursor overlaps a match; non-hover highlights follow. diff --git a/zellij-server/src/panes/terminal_pane.rs b/zellij-server/src/panes/terminal_pane.rs index f0fd3949..52be384d 100644 --- a/zellij-server/src/panes/terminal_pane.rs +++ b/zellij-server/src/panes/terminal_pane.rs @@ -976,6 +976,9 @@ impl Pane for TerminalPane { self.grid.set_hover_position(position); self.set_should_render(true); } + fn cached_hover_tooltip(&self) -> Option<String> { + self.grid.cached_hover_tooltip.clone() + } fn plugin_highlight_at( &self, position: &Position, diff --git a/zellij-server/src/panes/unit/grid_tests.rs b/zellij-server/src/panes/unit/grid_tests.rs index 7010cefb..d5ce6a6a 100644 --- a/zellij-server/src/panes/unit/grid_tests.rs +++ b/zellij-server/src/panes/unit/grid_tests.rs @@ -4581,6 +4581,7 @@ fn create_highlight( bold, italic, underline, + tooltip_text: None, } } @@ -4595,6 +4596,7 @@ fn set_plugin_regex_highlights_basic_match() { bold: false, italic: false, underline: true, + tooltip_text: None, }]; grid.set_plugin_regex_highlights(1, highlights, &Style::default()); let slot = grid.plugin_highlights.get(&1); @@ -4654,6 +4656,7 @@ fn upsert_replaces_same_pattern() { bold: false, italic: false, underline: true, + tooltip_text: None, }]; grid.set_plugin_regex_highlights(1, h1, &Style::default()); @@ -4665,6 +4668,7 @@ fn upsert_replaces_same_pattern() { bold: false, italic: false, underline: false, + tooltip_text: None, }]; grid.set_plugin_regex_highlights(1, h2, &Style::default()); @@ -4699,6 +4703,7 @@ fn plugin_highlight_at_returns_match() { bold: false, italic: false, underline: true, + tooltip_text: None, }]; grid.set_plugin_regex_highlights(1, highlights, &Style::default()); diff --git a/zellij-server/src/tab/mod.rs b/zellij-server/src/tab/mod.rs index 861a3103..59a79126 100644 --- a/zellij-server/src/tab/mod.rs +++ b/zellij-server/src/tab/mod.rs @@ -615,6 +615,9 @@ pub trait Pane { } fn clear_plugin_highlights(&mut self, _plugin_id: u32) {} fn set_hover_position(&mut self, _position: Option<Position>) {} + fn cached_hover_tooltip(&self) -> Option<String> { + None + } fn plugin_highlight_at( &self, _position: &Position, diff --git a/zellij-server/src/tab/mouse_handler.rs b/zellij-server/src/tab/mouse_handler.rs index f98dc3f4..5e575eb7 100644 --- a/zellij-server/src/tab/mouse_handler.rs +++ b/zellij-server/src/tab/mouse_handler.rs @@ -643,7 +643,29 @@ impl MouseHandler { } match action { - MouseAction::GroupToggle(pane_id) => Ok(MouseEffect::group_toggle(pane_id)), + MouseAction::GroupToggle(pane_id) => { + // Check if Alt+Click lands on a plugin highlight + if let Some(pane) = tab.get_pane_with_id_mut(pane_id) { + let relative_position = pane.relative_position(&event.position); + if let Some((hit_plugin_id, pattern, matched_string, context)) = + pane.plugin_highlight_at(&relative_position) + { + let _ = tab + .senders + .send_to_plugin(PluginInstruction::HighlightClicked { + plugin_id: hit_plugin_id, + client_id, + pane_id, + pattern, + matched_string, + context, + }); + return Ok(MouseEffect::state_changed()); + } + } + // No highlight hit — fall through to pane grouping + Ok(MouseEffect::group_toggle(pane_id)) + }, MouseAction::GroupAdd(pane_id) => Ok(MouseEffect::group_add(pane_id)), MouseAction::Ungroup => Ok(MouseEffect::ungroup()), MouseAction::StartResize { @@ -684,23 +706,6 @@ impl MouseHandler { .ok_or_else(|| anyhow!("Failed to find pane {pane_id:?}"))?; let relative_position = pane.relative_position(&position); - // Check if click lands on a plugin highlight - if let Some((hit_plugin_id, pattern, matched_string, context)) = - pane.plugin_highlight_at(&relative_position) - { - let _ = tab - .senders - .send_to_plugin(PluginInstruction::HighlightClicked { - plugin_id: hit_plugin_id, - client_id, - pane_id, - pattern, - matched_string, - context, - }); - return Ok(MouseEffect::state_changed()); - } - let mut leave_clipboard_message = false; pane.start_selection(&relative_position, client_id); if pane.get_selected_text(client_id).is_some() { diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__hover_over_highlight_with_tooltip_caches_tooltip-2.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__hover_over_highlight_with_tooltip_caches_tooltip-2.snap new file mode 100644 index 00000000..09b7a7e1 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__hover_over_highlight_with_tooltip_caches_tooltip-2.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +expression: snapshot_without_tooltip +--- +00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +01 (C): │hello link_text bar │ +02 (C): │ │ +03 (C): │ │ +04 (C): │ │ +05 (C): │ │ +06 (C): │ │ +07 (C): │ │ +08 (C): │ │ +09 (C): │ │ +10 (C): │ │ +11 (C): │ │ +12 (C): │ │ +13 (C): │ │ +14 (C): │ │ +15 (C): │ │ +16 (C): │ │ +17 (C): │ │ +18 (C): │ │ +19 (C): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__hover_over_highlight_with_tooltip_caches_tooltip.snap b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__hover_over_highlight_with_tooltip_caches_tooltip.snap new file mode 100644 index 00000000..6d1e2643 --- /dev/null +++ b/zellij-server/src/tab/unit/snapshots/zellij_server__tab__tab_integration_tests__hover_over_highlight_with_tooltip_caches_tooltip.snap @@ -0,0 +1,24 @@ +--- +source: zellij-server/src/tab/./unit/tab_integration_tests.rs +expression: snapshot_with_tooltip +--- +00 (C): ┌ Pane #1 ──────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ +01 (C): │hello link_text bar │ +02 (C): │ │ +03 (C): │ │ +04 (C): │ │ +05 (C): │ │ +06 (C): │ │ +07 (C): │ │ +08 (C): │ │ +09 (C): │ │ +10 (C): │ │ +11 (C): │ │ +12 (C): │ │ +13 (C): │ │ +14 (C): │ │ +15 (C): │ │ +16 (C): │ │ +17 (C): │ │ +18 (C): │ │ +19 (C): └ Alt <Click> - Open ───────────────────────────────────────────────────────────────────────────────────────────────────┘ diff --git a/zellij-server/src/tab/unit/tab_integration_tests.rs b/zellij-server/src/tab/unit/tab_integration_tests.rs index ca68538c..da7e7ba7 100644 --- a/zellij-server/src/tab/unit/tab_integration_tests.rs +++ b/zellij-server/src/tab/unit/tab_integration_tests.rs @@ -12581,6 +12581,7 @@ fn click_on_plugin_highlight_sends_highlight_clicked() { bold: false, italic: false, underline: true, + tooltip_text: None, }]; tab.set_plugin_regex_highlights_for_pane( PaneId::Terminal(1), @@ -12589,13 +12590,13 @@ fn click_on_plugin_highlight_sends_highlight_clicked() { &Style::default(), ); - // Left-click at position where "foo" appears + // Alt+Click at position where "foo" appears // With draw_pane_frames=true, content starts at row 1, col 1 // "click here foo bar" -> "foo" starts at offset 11, so col = 1 + 11 = 12 let click_position = Position::new(1, 12); - let effect = tab - .handle_mouse_event(&MouseEvent::new_left_press_event(click_position), client_id) - .unwrap(); + let mut alt_click = MouseEvent::new_left_press_event(click_position); + alt_click.alt = true; + let effect = tab.handle_mouse_event(&alt_click, client_id).unwrap(); assert!(effect.state_changed); @@ -12645,6 +12646,7 @@ fn click_outside_highlight_starts_normal_selection() { bold: false, italic: false, underline: true, + tooltip_text: None, }]; tab.set_plugin_regex_highlights_for_pane( PaneId::Terminal(1), @@ -12696,6 +12698,7 @@ fn hover_over_highlight_shows_styling() { bold: false, italic: true, underline: true, + tooltip_text: None, }]; tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 1, highlights, &Style::default()); @@ -12766,6 +12769,7 @@ fn hover_on_unfocused_pane_no_highlight() { bold: false, italic: true, underline: true, + tooltip_text: None, }]; tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 1, highlights, &Style::default()); @@ -12815,6 +12819,7 @@ fn highlight_click_in_mouse_mode_pane_suppressed() { bold: false, italic: false, underline: true, + tooltip_text: None, }]; tab.set_plugin_regex_highlights_for_pane( PaneId::Terminal(1), @@ -12890,6 +12895,7 @@ fn set_and_clear_highlights_across_tiled_and_floating() { bold: false, italic: false, underline: true, + tooltip_text: None, }]; tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 1, h1.clone(), &Style::default()); tab.set_plugin_regex_highlights_for_pane(pane2, 1, h1.clone(), &Style::default()); @@ -12904,6 +12910,7 @@ fn set_and_clear_highlights_across_tiled_and_floating() { bold: false, italic: false, underline: false, + tooltip_text: None, }]; tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 2, h2.clone(), &Style::default()); tab.set_plugin_regex_highlights_for_pane(pane2, 2, h2.clone(), &Style::default()); @@ -12927,3 +12934,167 @@ fn set_and_clear_highlights_across_tiled_and_floating() { ); assert_snapshot!(snapshot); } + +#[test] +fn plain_click_on_highlight_does_not_send_highlight_clicked() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let (mut tab, mock_plugin_receiver) = + create_new_tab_with_plugin_receiver(size, ModeInfo::default()); + + tab.handle_pty_bytes(1, Vec::from("click here foo bar".as_bytes())) + .unwrap(); + + let highlights = vec![RegexHighlight { + pattern: "foo".into(), + style: HighlightStyle::Emphasis0, + context: BTreeMap::new(), + on_hover: false, + bold: false, + italic: false, + underline: true, + tooltip_text: None, + }]; + tab.set_plugin_regex_highlights_for_pane( + PaneId::Terminal(1), + 42, + highlights, + &Style::default(), + ); + + // Plain left-click on "foo" — should start selection, NOT send HighlightClicked + let click_position = Position::new(1, 12); + let _effect = tab + .handle_mouse_event(&MouseEvent::new_left_press_event(click_position), client_id) + .unwrap(); + + let mut found_highlight_clicked = false; + while let Ok((instruction, _ctx)) = mock_plugin_receiver.try_recv() { + if matches!(instruction, PluginInstruction::HighlightClicked { .. }) { + found_highlight_clicked = true; + } + } + assert!( + !found_highlight_clicked, + "Plain click should NOT send HighlightClicked after Alt+Click change" + ); + + // Selection should have started instead + assert!(tab.selecting_with_mouse_in_pane.is_some()); +} + +#[test] +fn alt_click_outside_highlight_falls_through_to_group_toggle() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let (mut tab, mock_plugin_receiver) = + create_new_tab_with_plugin_receiver(size, ModeInfo::default()); + + tab.handle_pty_bytes(1, Vec::from("click here foo bar".as_bytes())) + .unwrap(); + + let highlights = vec![RegexHighlight { + pattern: "foo".into(), + style: HighlightStyle::Emphasis0, + context: BTreeMap::new(), + on_hover: false, + bold: false, + italic: false, + underline: true, + tooltip_text: None, + }]; + tab.set_plugin_regex_highlights_for_pane( + PaneId::Terminal(1), + 42, + highlights, + &Style::default(), + ); + + // Alt+Click on "click" (col 1), not on "foo" — should fall through to group toggle + let click_position = Position::new(1, 1); + let mut alt_click = MouseEvent::new_left_press_event(click_position); + alt_click.alt = true; + let effect = tab.handle_mouse_event(&alt_click, client_id).unwrap(); + + // No HighlightClicked should be sent + let mut found_highlight_clicked = false; + while let Ok((instruction, _ctx)) = mock_plugin_receiver.try_recv() { + if matches!(instruction, PluginInstruction::HighlightClicked { .. }) { + found_highlight_clicked = true; + } + } + assert!( + !found_highlight_clicked, + "Alt+Click outside highlight should NOT send HighlightClicked" + ); + + // Group toggle effect should have been produced + assert_eq!(effect.group_toggle, Some(PaneId::Terminal(1))); +} + +#[test] +fn hover_over_highlight_with_tooltip_caches_tooltip() { + let size = Size { + cols: 121, + rows: 20, + }; + let client_id = 1; + let mut tab = create_new_tab(size, ModeInfo::default()); + let mut output = Output::default(); + + tab.handle_pty_bytes(1, Vec::from("hello link_text bar".as_bytes())) + .unwrap(); + + let highlights = vec![RegexHighlight { + pattern: "link_text".into(), + style: HighlightStyle::None, + context: BTreeMap::new(), + on_hover: true, + bold: false, + italic: true, + underline: true, + tooltip_text: Some("Open".to_string()), + }]; + tab.set_plugin_regex_highlights_for_pane(PaneId::Terminal(1), 1, highlights, &Style::default()); + + // Hover over the match (link_text starts at offset 6, with frame offset col = 7) + let hover_position = Position::new(1, 9); + let _effect = tab + .handle_mouse_event( + &MouseEvent::new_buttonless_motion(hover_position), + client_id, + ) + .unwrap(); + + tab.render(&mut output, None).unwrap(); + let snapshot_with_tooltip = take_snapshot( + output.serialize().unwrap().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + // The bottom frame row should contain the tooltip text + assert_snapshot!(snapshot_with_tooltip); + + // Move hover away — tooltip should disappear + let mut output2 = Output::default(); + let far_position = Position::new(1, 1); + let _effect2 = tab + .handle_mouse_event(&MouseEvent::new_buttonless_motion(far_position), client_id) + .unwrap(); + + tab.render(&mut output2, None).unwrap(); + let snapshot_without_tooltip = take_snapshot( + output2.serialize().unwrap().get(&client_id).unwrap(), + size.rows, + size.cols, + Palette::default(), + ); + assert_snapshot!(snapshot_without_tooltip); +} diff --git a/zellij-server/src/ui/pane_boundaries_frame.rs b/zellij-server/src/ui/pane_boundaries_frame.rs index 57a69559..a464e4b0 100644 --- a/zellij-server/src/ui/pane_boundaries_frame.rs +++ b/zellij-server/src/ui/pane_boundaries_frame.rs @@ -66,6 +66,7 @@ pub struct FrameParams { pub mouse_is_hovering_over_pane: bool, pub pane_is_selectable: bool, pub show_help_text: bool, + pub highlight_tooltip: Option<String>, } #[derive(Default, PartialEq)] @@ -90,6 +91,7 @@ pub struct PaneFrame { mouse_is_hovering_over_pane: bool, is_selectable: bool, show_help_text: bool, + highlight_tooltip: Option<String>, } impl PaneFrame { @@ -120,6 +122,7 @@ impl PaneFrame { mouse_is_hovering_over_pane: frame_params.mouse_is_hovering_over_pane, is_selectable: frame_params.pane_is_selectable, show_help_text: frame_params.show_help_text, + highlight_tooltip: frame_params.highlight_tooltip, } } pub fn is_pinned(mut self, is_pinned: bool) -> Self { @@ -708,6 +711,36 @@ impl PaneFrame { .or_else(|| Some(self.title_line_without_middle())) .with_context(|| format!("failed to render title '{}'", self.title)) } + fn render_highlight_tooltip_undertitle(&self) -> Result<Vec<TerminalCharacter>> { + let max_undertitle_length = self.geom.cols.saturating_sub(2); + + let mut left_boundary = + foreground_color(self.get_corner(boundary_type::BOTTOM_LEFT), self.color); + let mut right_boundary = + foreground_color(self.get_corner(boundary_type::BOTTOM_RIGHT), self.color); + + let tooltip_text = self.highlight_tooltip.as_deref().unwrap_or(""); + let text = format!(" Alt <Click> - {} ", tooltip_text); + let text_len = text.chars().count(); + if text_len > max_undertitle_length { + return Ok(self.empty_undertitle(max_undertitle_length)); + } + + let mut text_characters = foreground_color(&text, self.color); + + let padding_len = max_undertitle_length.saturating_sub(text_len); + let mut padding = String::new(); + for _ in 0..padding_len { + padding.push_str(boundary_type::HORIZONTAL); + } + + let mut ret = vec![]; + ret.append(&mut left_boundary); + ret.append(&mut text_characters); + ret.append(&mut foreground_color(&padding, self.color)); + ret.append(&mut right_boundary); + Ok(ret) + } fn render_help_text_undertitle(&self) -> Result<Vec<TerminalCharacter>> { let max_undertitle_length = self.geom.cols.saturating_sub(2); @@ -932,7 +965,16 @@ impl PaneFrame { character_chunks.push(CharacterChunk::new(title, x, y)); } else if row == self.geom.rows - 1 { // bottom row - if self.show_help_text && self.is_main_client { + if self.highlight_tooltip.is_some() && self.is_main_client { + let x = self.geom.x; + let y = self.geom.y + row; + character_chunks.push(CharacterChunk::new( + self.render_highlight_tooltip_undertitle() + .with_context(err_context)?, + x, + y, + )); + } else if self.show_help_text && self.is_main_client { let x = self.geom.x; let y = self.geom.y + row; character_chunks.push(CharacterChunk::new( diff --git a/zellij-server/src/ui/pane_contents_and_ui.rs b/zellij-server/src/ui/pane_contents_and_ui.rs index 420ba6e4..694592af 100644 --- a/zellij-server/src/ui/pane_contents_and_ui.rs +++ b/zellij-server/src/ui/pane_contents_and_ui.rs @@ -227,6 +227,7 @@ impl<'a> PaneContentsAndUi<'a> { let pane_focused_for_differet_client = !other_focused_clients.is_empty(); let frame_color = self.frame_color(client_id, client_mode, session_is_mirrored); + let highlight_tooltip = self.pane.cached_hover_tooltip(); let focused_client = if pane_focused_for_client_id { Some(client_id) } else if pane_focused_for_differet_client { @@ -252,6 +253,7 @@ impl<'a> PaneContentsAndUi<'a> { .contains(&client_id), pane_is_selectable, show_help_text: self.show_help_text, + highlight_tooltip: highlight_tooltip.clone(), } } else { FrameParams { @@ -271,6 +273,7 @@ impl<'a> PaneContentsAndUi<'a> { .contains(&client_id), pane_is_selectable, show_help_text: self.show_help_text, + highlight_tooltip, } }; diff --git a/zellij-tile/src/shim.rs b/zellij-tile/src/shim.rs index 8c40b28a..167df3ec 100644 --- a/zellij-tile/src/shim.rs +++ b/zellij-tile/src/shim.rs @@ -2760,6 +2760,11 @@ pub fn hide_floating_panes(tab_id: Option<usize>) -> Result<bool, String> { /// render time. The plugin never handles coordinates, so there is no race /// condition between content changes and highlight application. /// +/// When `on_hover` is `true` and `tooltip_text` is `Some(...)`, the tooltip +/// text is displayed at the bottom of the pane frame (formatted as +/// `" Alt <Click> - {tooltip_text} "`) whenever the mouse cursor hovers over +/// the highlighted region. +/// /// Requires `ChangeApplicationState` permission. pub fn set_pane_regex_highlights(pane_id: PaneId, highlights: Vec<RegexHighlight>) { let plugin_command = PluginCommand::SetPaneRegexHighlights(pane_id, highlights); diff --git a/zellij-utils/assets/plugins/about.wasm b/zellij-utils/assets/plugins/about.wasm Binary files differindex 393ff56e..aa506b12 100755 --- a/zellij-utils/assets/plugins/about.wasm +++ b/zellij-utils/assets/plugins/about.wasm diff --git a/zellij-utils/assets/plugins/compact-bar.wasm b/zellij-utils/assets/plugins/compact-bar.wasm Binary files differindex a5ae98c5..b411e2f3 100755 --- a/zellij-utils/assets/plugins/compact-bar.wasm +++ b/zellij-utils/assets/plugins/compact-bar.wasm diff --git a/zellij-utils/assets/plugins/configuration.wasm b/zellij-utils/assets/plugins/configuration.wasm Binary files differindex bc47d8ce..5539c523 100755 --- a/zellij-utils/assets/plugins/configuration.wasm +++ b/zellij-utils/assets/plugins/configuration.wasm diff --git a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm Binary files differindex d84e92dd..c2212a76 100755 --- a/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm +++ b/zellij-utils/assets/plugins/fixture-plugin-for-tests.wasm diff --git a/zellij-utils/assets/plugins/layout-manager.wasm b/zellij-utils/assets/plugins/layout-manager.wasm Binary files differindex a7f0164f..e91f8970 100755 --- a/zellij-utils/assets/plugins/layout-manager.wasm +++ b/zellij-utils/assets/plugins/layout-manager.wasm diff --git a/zellij-utils/assets/plugins/link.wasm b/zellij-utils/assets/plugins/link.wasm Binary files differindex 0b8be5c3..f22e299a 100755 --- a/zellij-utils/assets/plugins/link.wasm +++ b/zellij-utils/assets/plugins/link.wasm diff --git a/zellij-utils/assets/plugins/multiple-select.wasm b/zellij-utils/assets/plugins/multiple-select.wasm Binary files differindex 45bb9a2a..47298430 100755 --- a/zellij-utils/assets/plugins/multiple-select.wasm +++ b/zellij-utils/assets/plugins/multiple-select.wasm diff --git a/zellij-utils/assets/plugins/plugin-manager.wasm b/zellij-utils/assets/plugins/plugin-manager.wasm Binary files differindex 706563c1..918a7087 100755 --- a/zellij-utils/assets/plugins/plugin-manager.wasm +++ b/zellij-utils/assets/plugins/plugin-manager.wasm diff --git a/zellij-utils/assets/plugins/session-manager.wasm b/zellij-utils/assets/plugins/session-manager.wasm Binary files differindex 04494e13..d4e29478 100755 --- a/zellij-utils/assets/plugins/session-manager.wasm +++ b/zellij-utils/assets/plugins/session-manager.wasm diff --git a/zellij-utils/assets/plugins/share.wasm b/zellij-utils/assets/plugins/share.wasm Binary files differindex 9ee67571..4333880d 100755 --- a/zellij-utils/assets/plugins/share.wasm +++ b/zellij-utils/assets/plugins/share.wasm diff --git a/zellij-utils/assets/plugins/status-bar.wasm b/zellij-utils/assets/plugins/status-bar.wasm Binary files differindex 69036d5c..8476c66d 100755 --- a/zellij-utils/assets/plugins/status-bar.wasm +++ b/zellij-utils/assets/plugins/status-bar.wasm diff --git a/zellij-utils/assets/plugins/strider.wasm b/zellij-utils/assets/plugins/strider.wasm Binary files differindex 5e457f65..7aa561df 100755 --- a/zellij-utils/assets/plugins/strider.wasm +++ b/zellij-utils/assets/plugins/strider.wasm diff --git a/zellij-utils/assets/plugins/tab-bar.wasm b/zellij-utils/assets/plugins/tab-bar.wasm Binary files differindex 6e111f04..af1e7cc5 100755 --- a/zellij-utils/assets/plugins/tab-bar.wasm +++ b/zellij-utils/assets/plugins/tab-bar.wasm diff --git a/zellij-utils/assets/prost/api.plugin_command.rs b/zellij-utils/assets/prost/api.plugin_command.rs index 2296b2da..eb26fcbd 100644 --- a/zellij-utils/assets/prost/api.plugin_command.rs +++ b/zellij-utils/assets/prost/api.plugin_command.rs @@ -1860,6 +1860,8 @@ pub struct RegexHighlight { pub italic: bool, #[prost(bool, tag="7")] pub underline: bool, + #[prost(string, optional, tag="8")] + pub tooltip_text: ::core::option::Option<::prost::alloc::string::String>, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/zellij-utils/src/data.rs b/zellij-utils/src/data.rs index 64f1d33c..67af1d8a 100644 --- a/zellij-utils/src/data.rs +++ b/zellij-utils/src/data.rs @@ -1236,6 +1236,7 @@ pub struct RegexHighlight { pub bold: bool, pub italic: bool, pub underline: bool, + pub tooltip_text: Option<String>, // shown at bottom of pane frame when hovering over match } // these are used for the web client diff --git a/zellij-utils/src/plugin_api/plugin_command.proto b/zellij-utils/src/plugin_api/plugin_command.proto index c4fa949f..89836c90 100644 --- a/zellij-utils/src/plugin_api/plugin_command.proto +++ b/zellij-utils/src/plugin_api/plugin_command.proto @@ -1263,6 +1263,7 @@ message RegexHighlight { bool bold = 5; bool italic = 6; bool underline = 7; + optional string tooltip_text = 8; } message SetPaneRegexHighlightsPayload { diff --git a/zellij-utils/src/plugin_api/plugin_command.rs b/zellij-utils/src/plugin_api/plugin_command.rs index b535e14a..2ce11c99 100644 --- a/zellij-utils/src/plugin_api/plugin_command.rs +++ b/zellij-utils/src/plugin_api/plugin_command.rs @@ -2515,6 +2515,7 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand { bold: h.bold, italic: h.italic, underline: h.underline, + tooltip_text: h.tooltip_text, } }) .collect(); @@ -4165,6 +4166,7 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand { bold: h.bold, italic: h.italic, underline: h.underline, + tooltip_text: h.tooltip_text, } }) .collect(), |
