diff options
Diffstat (limited to 'src/ui/panes.rs')
| -rw-r--r-- | src/ui/panes.rs | 306 |
1 files changed, 36 insertions, 270 deletions
diff --git a/src/ui/panes.rs b/src/ui/panes.rs index f20b55b5..cd5ea34c 100644 --- a/src/ui/panes.rs +++ b/src/ui/panes.rs @@ -1,8 +1,8 @@ use ratatui::{ + buffer::Buffer, layout::Rect, style::{Color, Modifier, Style}, - text::{Line, Span}, - widgets::{Block, Borders, Clear, Paragraph}, + widgets::{Block, Borders}, Frame, }; @@ -12,7 +12,7 @@ use super::text::display_width; use super::text::truncate_end; use super::widgets::panel_contrast_fg; use crate::app::state::Palette; -use crate::app::{AppState, Mode}; +use crate::app::AppState; use crate::layout::PaneInfo; use crate::popup_size::resolve_popup_geometry; use crate::terminal::{TerminalRuntime, TerminalRuntimeRegistry}; @@ -352,60 +352,13 @@ pub(super) fn render_panes( return; }; - let multi_pane = ws.layout.pane_count() > 1; - let terminal_active = app.mode == Mode::Terminal; - for info in pane_infos { if let Some(rt) = app.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, info.id) { let show_cursor = info.is_focused - && terminal_active && !pane_is_scrolled_back(rt) && app.pane_exposes_host_cursor(ws_idx, info.id); rt.render(frame, info.inner_rect, show_cursor); render_pane_scrollbar(app, frame, info, rt); - - let should_dim = !info.is_focused && multi_pane && !terminal_active; - if should_dim { - let inner = info.inner_rect; - let buf = frame.buffer_mut(); - for y in inner.y..inner.y + inner.height { - for x in inner.x..inner.x + inner.width { - let cell = &mut buf[(x, y)]; - cell.set_style(cell.style().add_modifier(Modifier::DIM)); - } - } - } - - let (copy_search_top, copy_search_bottom, copy_search_matches) = - validated_copy_mode_search_matches(app, info, rt); - render_copy_mode_search_highlights( - app, - frame, - info, - copy_search_top, - copy_search_bottom, - ©_search_matches, - false, - ); - render_selection_highlight( - &app.selection, - frame, - info.id, - info.inner_rect, - rt.scroll_metrics(), - &app.palette, - app.host_terminal_theme, - ); - render_copy_mode_search_highlights( - app, - frame, - info, - copy_search_top, - copy_search_bottom, - ©_search_matches, - true, - ); - render_copy_mode_cursor(app, frame, info); } } @@ -443,36 +396,6 @@ pub(super) fn resize_popup_pane( } } -pub(super) fn render_popup_pane( - app: &AppState, - terminal_runtimes: &TerminalRuntimeRegistry, - frame: &mut Frame, - area: Rect, -) { - let Some(popup) = app.popup_pane.as_ref() else { - return; - }; - let Some((outer, inner)) = popup_pane_rects(app, area) else { - return; - }; - let Some(rt) = terminal_runtimes.get(&popup.terminal_id) else { - return; - }; - let title = app - .terminals - .get(&popup.terminal_id) - .and_then(|terminal| terminal.manual_label.as_deref()) - .unwrap_or("popup"); - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(app.palette.accent)) - .title(pane_border_title(title, outer.width, true).unwrap_or_default()) - .style(Style::default().bg(app.palette.panel_bg)); - frame.render_widget(Clear, outer); - frame.render_widget(block, outer); - rt.render(frame, inner, !pane_is_scrolled_back(rt)); -} - #[derive(Clone, Copy, Default)] struct LineCell { up: bool, @@ -724,141 +647,25 @@ fn line_cell_symbol(line: LineCell) -> &'static str { } } -fn render_copy_mode_cursor(app: &AppState, frame: &mut Frame, info: &PaneInfo) { - if app.mode != Mode::Copy { - return; - } - let Some(copy_mode) = app.copy_mode.as_ref() else { - return; - }; - if copy_mode.pane_id != info.id - || copy_mode.cursor_row >= info.inner_rect.height - || copy_mode.cursor_col >= info.inner_rect.width - { - return; - } - - let x = info.inner_rect.x + copy_mode.cursor_col; - let y = info.inner_rect.y + copy_mode.cursor_row; - let cell = &mut frame.buffer_mut()[(x, y)]; - cell.set_style( - Style::default() - .fg(panel_contrast_fg(&app.palette)) - .bg(app.palette.accent) - .add_modifier(Modifier::BOLD), - ); -} - -fn validated_copy_mode_search_matches( - app: &AppState, - info: &PaneInfo, - rt: &crate::terminal::TerminalRuntime, -) -> (u32, u32, Vec<(usize, crate::pane::TerminalTextMatch)>) { - let Some(copy_mode) = app.copy_mode.as_ref() else { - return (0, 0, Vec::new()); - }; - if copy_mode.pane_id != info.id { - return (0, 0, Vec::new()); - } - let Some(metrics) = rt.scroll_metrics() else { - return (0, 0, Vec::new()); - }; - let top = metrics - .max_offset_from_bottom - .saturating_sub(metrics.offset_from_bottom) - .min(u32::MAX as usize) as u32; - let bottom = top.saturating_add(u32::from(info.inner_rect.height.saturating_sub(1))); - let first_visible = copy_mode - .search - .matches - .partition_point(|text_match| text_match.end.row < top); - let visible = ©_mode.search.matches[first_visible..]; - let visible_len = visible.partition_point(|text_match| text_match.start.row <= bottom); - let candidates = visible[..visible_len].to_vec(); - let validity = rt.text_matches_are_current(&candidates); - - let matches = candidates - .into_iter() - .zip(validity) - .enumerate() - .filter_map(|(offset, (text_match, is_current))| { - is_current.then_some((first_visible + offset, text_match)) - }) - .collect(); - (top, bottom, matches) -} - -fn render_copy_mode_search_highlights( - app: &AppState, - frame: &mut Frame, - info: &PaneInfo, - top: u32, - bottom: u32, - matches: &[(usize, crate::pane::TerminalTextMatch)], - current_only: bool, -) { - let Some(copy_mode) = app.copy_mode.as_ref() else { - return; - }; - let current = copy_mode.search.current; - let style = if current_only { - Style::default() - .fg(panel_contrast_fg(&app.palette)) - .bg(app.palette.accent) - .add_modifier(Modifier::BOLD) - } else { - Style::default() - .fg(app.palette.text) - .bg(app.palette.surface1) - }; - - for &(index, text_match) in matches { - if (current == Some(index)) != current_only { - continue; - } - let start_row = text_match.start.row.max(top); - let end_row = text_match.end.row.min(bottom); - for absolute_row in start_row..=end_row { - let viewport_row = absolute_row.saturating_sub(top) as u16; - let start_col = if absolute_row == text_match.start.row { - text_match.start.col - } else { - 0 - }; - let end_col = if absolute_row == text_match.end.row { - text_match.end.col - } else { - info.inner_rect.width.saturating_sub(1) - }; - for col in start_col..=end_col.min(info.inner_rect.width.saturating_sub(1)) { - let x = info.inner_rect.x.saturating_add(col); - let y = info.inner_rect.y.saturating_add(viewport_row); - frame.buffer_mut()[(x, y)].set_style(style); - } - } - } -} - -fn render_selection_highlight( - selection: &Option<crate::selection::Selection>, - frame: &mut Frame, - pane_id: crate::layout::PaneId, +pub(crate) fn render_selection_highlight<P: PartialEq>( + selection: Option<&crate::selection::Selection<P>>, + buffer: &mut Buffer, + pane_id: &P, inner: Rect, scroll_metrics: Option<crate::pane::ScrollMetrics>, p: &Palette, host_theme: crate::terminal_theme::TerminalTheme, ) { - if let Some(sel) = selection { - if sel.is_visible() && sel.pane_id == pane_id { - let buf = frame.buffer_mut(); - let style = automatic_selection_style(p, host_theme); - for y in 0..inner.height { - for x in 0..inner.width { - if sel.contains(y, x, scroll_metrics) { - let cell = &mut buf[(inner.x + x, inner.y + y)]; - cell.set_style(style); - } - } + let Some(selection) = + selection.filter(|selection| selection.is_visible() && &selection.pane_id == pane_id) + else { + return; + }; + let style = automatic_selection_style(p, host_theme); + for y in 0..inner.height { + for x in 0..inner.width { + if selection.contains(y, x, scroll_metrics) { + buffer[(inner.x + x, inner.y + y)].set_style(style); } } } @@ -959,47 +766,6 @@ fn color_to_rgb(color: Color) -> Option<Rgb> { } } -pub(super) fn render_empty(app: &AppState, frame: &mut Frame, area: Rect) { - let p = &app.palette; - let lines = vec![ - Line::from(""), - Line::from(""), - Line::from(Span::styled( - " No workspaces yet", - Style::default().fg(p.overlay0), - )), - Line::from(""), - Line::from(Span::styled( - " A workspace is one project context.", - Style::default().fg(p.overlay1), - )), - Line::from(Span::styled( - " Its root pane (top-left) sets the default repo or folder name.", - Style::default().fg(p.overlay1), - )), - Line::from(""), - Line::from(vec![ - Span::styled(" Press ", Style::default().fg(p.overlay0)), - Span::styled( - app.keybinds - .new_workspace - .label() - .unwrap_or_else(|| "unset".to_string()), - Style::default().fg(p.accent).add_modifier(Modifier::BOLD), - ), - Span::styled(" to create one", Style::default().fg(p.overlay0)), - ]), - ]; - frame.render_widget( - Paragraph::new(lines).block( - Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(p.surface_dim)), - ), - area, - ); -} - #[cfg(test)] mod tests { use super::*; @@ -1009,14 +775,13 @@ mod tests { use crate::terminal::TerminalState; use crate::workspace::Workspace; - fn render_view_pane_borders(app: &AppState, ws: &Workspace, frame: &mut Frame) { - render_pane_borders( - app, - ws, - &app.view.pane_infos, - &app.view.split_borders, - frame, - ); + fn render_view_pane_borders( + app: &AppState, + ws: &Workspace, + split_borders: &[crate::layout::SplitBorder], + frame: &mut Frame, + ) { + render_pane_borders(app, ws, &app.view.pane_infos, split_borders, frame); } #[test] @@ -1052,7 +817,6 @@ mod tests { #[test] fn pane_border_renderer_places_adjacent_cjk_by_display_width() { let mut app = AppState::test_new(); - app.mode = Mode::Terminal; app.view.terminal_area = Rect::new(0, 0, 12, 3); let ws = Workspace::test_new("test"); let pane_id = ws.tabs[0].root_pane; @@ -1073,7 +837,7 @@ mod tests { let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(12, 3)).unwrap(); terminal - .draw(|frame| render_view_pane_borders(&app, &ws, frame)) + .draw(|frame| render_view_pane_borders(&app, &ws, &[], frame)) .unwrap(); let buffer = terminal.backend().buffer(); @@ -1208,7 +972,6 @@ mod tests { #[test] fn global_pane_border_renderer_composes_junctions_and_focus_style() { let mut app = AppState::test_new(); - app.mode = Mode::Terminal; app.view.terminal_area = Rect::new(0, 0, 4, 4); app.view.pane_infos = vec![ PaneInfo { @@ -1244,7 +1007,7 @@ mod tests { is_focused: false, }, ]; - app.view.split_borders = vec![ + let split_borders = vec![ crate::layout::SplitBorder { pos: 2, direction: ratatui::layout::Direction::Horizontal, @@ -1265,7 +1028,7 @@ mod tests { ratatui::Terminal::new(ratatui::backend::TestBackend::new(4, 4)).unwrap(); terminal - .draw(|frame| render_view_pane_borders(&app, &ws, frame)) + .draw(|frame| render_view_pane_borders(&app, &ws, &split_borders, frame)) .unwrap(); let buffer = terminal.backend().buffer(); @@ -1278,7 +1041,6 @@ mod tests { #[test] fn gapped_pane_focus_does_not_color_neighbor_border() { let mut app = AppState::test_new(); - app.mode = Mode::Terminal; app.pane_gaps = true; app.view.terminal_area = Rect::new(0, 0, 4, 3); app.view.pane_infos = vec![ @@ -1304,7 +1066,7 @@ mod tests { ratatui::Terminal::new(ratatui::backend::TestBackend::new(4, 3)).unwrap(); terminal - .draw(|frame| render_view_pane_borders(&app, &ws, frame)) + .draw(|frame| render_view_pane_borders(&app, &ws, &[], frame)) .unwrap(); let buffer = terminal.backend().buffer(); @@ -1532,7 +1294,11 @@ mod tests { ..Default::default() }; let expected_style = automatic_selection_style(&palette, host_theme); - let selection = Some(Selection::range(PaneId::from_raw(1), 0, 0, 2, None)); + let selection = Some(Selection::absolute_range( + PaneId::from_raw(1), + (0, 0), + (0, 2), + )); let backend = ratatui::backend::TestBackend::new(4, 1); let mut terminal = ratatui::Terminal::new(backend).unwrap(); @@ -1552,9 +1318,9 @@ mod tests { ); buf[(2, 0)].set_style(Style::default().fg(Color::Blue).bg(Color::Reset)); render_selection_highlight( - &selection, - frame, - PaneId::from_raw(1), + selection.as_ref(), + frame.buffer_mut(), + &PaneId::from_raw(1), Rect::new(0, 0, 4, 1), None, &palette, |
