diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-19 18:59:43 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-19 18:59:43 +0300 |
| commit | 46a2b259a71828e7fcdeab1f67ef675304e2ca14 (patch) | |
| tree | 0b8aa654d8977ff24ecb25763e961d4a6fa78615 /src/ui/dialogs.rs | |
| parent | 26c5f97a908c1ebe4228302d1b9e8c5423b2ab75 (diff) | |
fix: defer api worktree operations
refs #686
refs #657
refs #662
refs https://github.com/ogulcancelik/herdr/discussions/687
Diffstat (limited to 'src/ui/dialogs.rs')
| -rw-r--r-- | src/ui/dialogs.rs | 84 |
1 files changed, 76 insertions, 8 deletions
diff --git a/src/ui/dialogs.rs b/src/ui/dialogs.rs index 31275282..51e5fb15 100644 --- a/src/ui/dialogs.rs +++ b/src/ui/dialogs.rs @@ -2,7 +2,7 @@ use ratatui::{ layout::{Constraint, Layout, Rect}, style::{Modifier, Style}, text::{Line, Span}, - widgets::{Clear, Paragraph}, + widgets::{Clear, Paragraph, Wrap}, Frame, }; @@ -12,6 +12,9 @@ use super::widgets::{ }; use crate::app::{state::WorktreeOpenState, AppState, Mode}; +const NEW_LINKED_WORKTREE_POPUP_WIDTH: u16 = 68; +const NEW_LINKED_WORKTREE_POPUP_HEIGHT: u16 = 12; + fn truncate_text(text: &str, max_width: usize) -> String { let len = text.chars().count(); if len <= max_width { @@ -126,7 +129,12 @@ pub(super) fn render_rename_overlay(app: &AppState, frame: &mut Frame, area: Rec } pub(crate) fn new_linked_worktree_inner_rect(area: Rect) -> Option<Rect> { - centered_popup_rect(area, 68, 10).map(|popup| { + centered_popup_rect( + area, + NEW_LINKED_WORKTREE_POPUP_WIDTH, + NEW_LINKED_WORKTREE_POPUP_HEIGHT, + ) + .map(|popup| { Rect::new( popup.x + 1, popup.y + 1, @@ -240,10 +248,16 @@ pub(super) fn render_new_linked_worktree_overlay(app: &AppState, frame: &mut Fra }; super::dim_background(frame, area); - let Some(inner) = render_modal_shell(frame, area, 68, 10, &app.palette) else { + let Some(inner) = render_modal_shell( + frame, + area, + NEW_LINKED_WORKTREE_POPUP_WIDTH, + NEW_LINKED_WORKTREE_POPUP_HEIGHT, + &app.palette, + ) else { return; }; - if inner.height < 7 { + if inner.height < 9 { return; } @@ -253,7 +267,7 @@ pub(super) fn render_new_linked_worktree_overlay(app: &AppState, frame: &mut Fra Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), - Constraint::Length(1), + Constraint::Length(3), Constraint::Length(1), Constraint::Min(0), ]) @@ -293,7 +307,9 @@ pub(super) fn render_new_linked_worktree_overlay(app: &AppState, frame: &mut Fra ); } else if let Some(error) = &create.error { frame.render_widget( - Paragraph::new(format!(" {error}")).style(Style::default().fg(app.palette.red)), + Paragraph::new(format!(" {error}")) + .style(Style::default().fg(app.palette.red)) + .wrap(Wrap { trim: false }), rows[5], ); } @@ -755,9 +771,13 @@ pub(crate) fn confirm_close_button_rects(inner: Rect) -> (Rect, Rect) { #[cfg(test)] mod tests { - use crate::{app::AppState, workspace::Workspace}; + use crate::{ + app::{state::WorktreeCreateState, AppState}, + workspace::Workspace, + }; + use ratatui::{backend::TestBackend, layout::Rect, Terminal}; - use super::confirm_close_overlay_text; + use super::{confirm_close_overlay_text, render_new_linked_worktree_overlay}; #[test] fn confirm_close_text_reports_parent_group_scope() { @@ -786,4 +806,52 @@ mod tests { assert_eq!(title, "Close worktree group?"); assert_eq!(detail, "main — 2 workspaces, 2 panes"); } + + #[test] + fn new_worktree_error_renders_fatal_stderr_line() { + let mut app = AppState::test_new(); + app.name_input = "foo".into(); + app.worktree_create = Some(WorktreeCreateState { + source_workspace_id: "source".into(), + source_checkout_path: "/repo/herdr".into(), + source_existing_membership: None, + source_repo_root: "/repo/herdr".into(), + repo_key: "repo-key".into(), + repo_name: "herdr".into(), + branch: "foo".into(), + checkout_path: "/repo/.worktrees/herdr/foo".into(), + error: Some( + "Preparing worktree (new branch 'foo')\nfatal: a branch named 'foo' already exists" + .into(), + ), + creating: false, + }); + + let mut terminal = + Terminal::new(TestBackend::new(100, 30)).expect("test terminal should initialize"); + terminal + .draw(|frame| render_new_linked_worktree_overlay(&app, frame, Rect::new(0, 0, 100, 30))) + .expect("new worktree overlay should render"); + let rendered = terminal + .backend() + .buffer() + .content() + .iter() + .map(|cell| cell.symbol()) + .collect::<String>(); + + assert!(rendered.contains("fatal: a branch named 'foo' already exists")); + } + + #[test] + fn new_worktree_hit_test_geometry_matches_modal_size() { + let area = Rect::new(0, 0, 100, 30); + let inner = super::new_linked_worktree_inner_rect(area).unwrap(); + let (create, cancel) = super::new_linked_worktree_button_rects(inner); + + assert_eq!(inner.width, super::NEW_LINKED_WORKTREE_POPUP_WIDTH - 2); + assert_eq!(inner.height, super::NEW_LINKED_WORKTREE_POPUP_HEIGHT - 2); + assert_eq!(create.y, inner.y + inner.height - 1); + assert_eq!(cancel.y, inner.y + inner.height - 1); + } } |
