From c03ce50c826e39213dfc71519ed6de783eb92f90 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Wed, 2 Sep 2026 13:51:45 +0700 Subject: [PATCH] update ui --- .../src/views/repo_detail/new_pull_request.rs | 194 +++++++++--------- 1 file changed, 102 insertions(+), 92 deletions(-) diff --git a/crates/workspace/src/views/repo_detail/new_pull_request.rs b/crates/workspace/src/views/repo_detail/new_pull_request.rs index 81c4d9c..f7362c1 100644 --- a/crates/workspace/src/views/repo_detail/new_pull_request.rs +++ b/crates/workspace/src/views/repo_detail/new_pull_request.rs @@ -15,7 +15,7 @@ use gpui::{ Pixels, Render, SharedString, Size, Subscription, Task, WeakEntity, Window, div, px, relative, size, }; -use gpui_base::Button as BaseButton; +use gpui_base::{Button as BaseButton, StyledExt}; use gpui_component::button::{Button, ButtonVariants}; use gpui_component::combobox::{ Caret, Combobox, ComboboxEvent, ComboboxState, ComboboxTriggerContext, @@ -97,10 +97,9 @@ impl NewPullRequestView { window: &mut Window, cx: &mut Context, ) -> Self { - let subject = cx.new(|cx| InputState::new(window, cx).placeholder("Pull request title")); - let description = cx - .new(|cx| TextareaState::new(window, cx).placeholder("Describe the change (optional)")); let pane = cx.new(DiffPane::new); + let subject = cx.new(|cx| InputState::new(window, cx).placeholder("Title")); + let description = cx.new(|cx| TextareaState::new(window, cx).placeholder("Describe...")); let base_select: Entity>> = cx.new(|cx| { ComboboxState::new( @@ -111,6 +110,7 @@ impl NewPullRequestView { ) .searchable(true) }); + let compare_select: Entity>> = cx.new(|cx| { ComboboxState::new( SearchableVec::new(Vec::::new()), @@ -181,8 +181,6 @@ impl NewPullRequestView { /// (defaults: the announced HEAD branch for the base, the checkout's /// current branch for the compare) and load the compare. fn choose_checkout(&mut self, window: &mut Window, cx: &mut Context) { - let handle = window.window_handle(); - let prompt = cx.prompt_for_paths(PathPromptOptions { files: false, directories: true, @@ -190,32 +188,36 @@ impl NewPullRequestView { prompt: Some("Choose local checkout".into()), }); - let task = cx.spawn(async move |this, cx| { - if let Ok(Ok(Some(mut paths))) = prompt.await - && let Some(path) = paths.pop() - { - let path = path.to_string_lossy().to_string(); - // Branches and the current branch are read off the UI thread. - let info = cx - .background_executor() - .spawn({ - let path = path.clone(); - async move { - let repo = gix::open(Path::new(&path)).ok()?; - let branches = - signed_git::worktree_branches(Path::new(&path)).unwrap_or_default(); - let current = signed_git::current_branch(&repo).ok().flatten(); - Some((branches, current)) - } - }) - .await; + let task = cx.spawn_in(window, async move |this, cx| { + // `Ok(Ok(Some(paths)))` means the user picked a folder; a + // cancel (or a picker failure) resolves to anything else. + let picked = match prompt.await { + Ok(Ok(Some(mut paths))) => paths.pop(), + _ => None, + }; + let Some(path) = picked else { + return Ok(()); + }; + let path = path.to_string_lossy().to_string(); + + // Branches and the current branch are read off the UI thread. + let info = cx + .background_spawn({ + let path = path.clone(); + async move { + let repo = gix::open(Path::new(&path)).ok()?; + let branches = + signed_git::worktree_branches(Path::new(&path)).unwrap_or_default(); + let current = signed_git::current_branch(&repo).ok().flatten(); + Some((branches, current)) + } + }) + .await; + + this.update_in(cx, |this, window, cx| { + this.apply_checkout(path, info, window, cx); + })?; - let _ = handle.update(cx, |_, window, cx| { - let _ = this.update(cx, |this, cx| { - this.apply_checkout(path, info, window, cx); - }); - }); - } Ok(()) }); self.tasks.push(task); @@ -496,54 +498,59 @@ impl NewPullRequestView { h_flex() .px_4() - .h_12() + .h_16() .w_full() .gap_2() - .items_center() + .items_end() .child( - div() - .text_xs() - .text_color(cx.theme().muted_foreground) - .child("base"), + v_flex() + .gap_1() + .child( + div() + .text_xs() + .font_semibold() + .text_color(cx.theme().muted_foreground) + .child("Merge Into"), + ) + .child( + div().w(px(140.)).child( + Combobox::new(&self.base_select) + .placeholder("branch") + .appearance(false) + .menu_width(px(220.)) + .disabled(!has_checkout) + .bg(cx.theme().muted) + .rounded(cx.theme().radius) + .render_trigger(|ctx, _window, cx| { + render_ref_trigger(ctx, CustomIconName::GitBranch, cx) + }), + ), + ), ) .child( - div().w(px(140.)).child( - Combobox::new(&self.base_select) - .placeholder("branch") - .appearance(false) - .menu_width(px(220.)) - .disabled(!has_checkout) - .bg(cx.theme().muted) - .rounded(cx.theme().radius) - .render_trigger(|ctx, _window, cx| { - render_ref_trigger(ctx, CustomIconName::GitBranch, cx) - }), - ), - ) - .child( - Icon::new(IconName::ArrowRight) - .small() - .text_color(cx.theme().muted_foreground), - ) - .child( - div() - .text_xs() - .text_color(cx.theme().muted_foreground) - .child("compare"), - ) - .child( - div().w(px(140.)).child( - Combobox::new(&self.compare_select) - .placeholder("branch") - .appearance(false) - .menu_width(px(220.)) - .disabled(!has_checkout) - .bg(cx.theme().muted) - .rounded(cx.theme().radius) - .render_trigger(|ctx, _window, cx| { - render_ref_trigger(ctx, CustomIconName::GitBranch, cx) - }), - ), + v_flex() + .gap_1() + .child( + div() + .text_xs() + .font_semibold() + .text_color(cx.theme().muted_foreground) + .child("Pull From"), + ) + .child( + div().w(px(140.)).child( + Combobox::new(&self.compare_select) + .placeholder("branch") + .appearance(false) + .menu_width(px(220.)) + .disabled(!has_checkout) + .bg(cx.theme().muted) + .rounded(cx.theme().radius) + .render_trigger(|ctx, _window, cx| { + render_ref_trigger(ctx, CustomIconName::GitBranch, cx) + }), + ), + ), ) .child( Button::new("choose-checkout") @@ -561,7 +568,7 @@ impl NewPullRequestView { .child( Button::new("create-pr") .primary() - .label("Create pull request") + .icon(IconName::Plus) .loading(self.submitting) .disabled(!can_submit) .on_click(cx.listener(|this, _event, window, cx| { @@ -575,11 +582,10 @@ impl NewPullRequestView { fn render_inputs(&self, _cx: &mut Context) -> AnyElement { v_flex() .px_4() - .py_2() .w_full() .gap_2() .child(Input::new(&self.subject)) - .child(Textarea::new(&self.description).h_32()) + .child(Textarea::new(&self.description).h_24()) .into_any_element() } @@ -590,7 +596,7 @@ impl NewPullRequestView { h_flex() .px_4() - .h_9() + .pb_4() .w_full() .gap_2() .items_center() @@ -834,20 +840,24 @@ impl Render for NewPullRequestView { v_flex() .id("new-pr") .size_full() - .child(self.render_compare_bar(cx)) - .child(self.render_inputs(cx)) - .when_some(self.error.clone(), |this, error| { - this.child( - h_flex() - .px_4() - .py_1() - .w_full() - .text_xs() - .text_color(cx.theme().danger) - .child(error), - ) - }) - .child(self.render_tabs(cx)) + .child( + v_flex() + .gap_4() + .child(self.render_compare_bar(cx)) + .child(self.render_inputs(cx)) + .when_some(self.error.clone(), |this, error| { + this.child( + h_flex() + .px_4() + .py_1() + .w_full() + .text_xs() + .text_color(cx.theme().danger) + .child(error), + ) + }) + .child(self.render_tabs(cx)), + ) .child( v_flex() .flex_1()