Compare commits

...
Sign in to create a new pull request.

10 commits

Author SHA1 Message Date
Lukas Wirth
e56672e542 impl Focusable for Button 2025-08-13 16:41:56 +02:00
Lukas Wirth
6e540a58fa Make tab cycling behave symmetrical in search buffer 2025-08-13 14:57:28 +02:00
Lukas Wirth
9b6b7f1412 Remove style differences between project and buffer search 2025-08-13 13:36:12 +02:00
Lukas Wirth
3cda09f875 More cleanups 2025-08-13 13:36:12 +02:00
Lukas Wirth
e2ab26ef41 Cleanup render_action_button some more 2025-08-13 12:08:07 +02:00
Lukas Wirth
88daa45537 Rearrange for clarity 2025-08-13 11:56:49 +02:00
Lukas Wirth
036bc75799 Deduplicate replace button code in search bars 2025-08-13 11:47:33 +02:00
Lukas Wirth
ccd5fc20bd Deduplicate prev/next matches button code in search bars 2025-08-13 11:41:00 +02:00
Lukas Wirth
bce501c696 Render query text red in project search if no results are found 2025-08-13 11:23:57 +02:00
Lukas Wirth
e354159f77 Fix replace button in project-search not rendering as filled when active 2025-08-13 10:38:36 +02:00
110 changed files with 1015 additions and 1067 deletions

View file

@ -1415,7 +1415,7 @@ impl AcpThreadView {
.text_color(cx.theme().colors().text_muted) .text_color(cx.theme().colors().text_muted)
.child(self.render_markdown(markdown, default_markdown_style(false, window, cx))) .child(self.render_markdown(markdown, default_markdown_style(false, window, cx)))
.child( .child(
Button::new(button_id, "Collapse Output") Button::new(button_id, "Collapse Output", cx)
.full_width() .full_width()
.style(ButtonStyle::Outlined) .style(ButtonStyle::Outlined)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
@ -1455,7 +1455,7 @@ impl AcpThreadView {
.border_color(self.tool_card_border_color(cx)) .border_color(self.tool_card_border_color(cx))
.overflow_hidden() .overflow_hidden()
.child( .child(
Button::new(button_id, label) Button::new(button_id, label, cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.color(Color::Muted) .color(Color::Muted)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
@ -1498,7 +1498,7 @@ impl AcpThreadView {
) )
.child(h_flex().gap_0p5().children(options.iter().map(|option| { .child(h_flex().gap_0p5().children(options.iter().map(|option| {
let option_id = SharedString::from(option.id.0.clone()); let option_id = SharedString::from(option.id.0.clone());
Button::new((option_id, entry_ix), option.name.clone()) Button::new((option_id, entry_ix), option.name.clone(), cx)
.map(|this| match option.kind { .map(|this| match option.kind {
acp::PermissionOptionKind::AllowOnce => { acp::PermissionOptionKind::AllowOnce => {
this.icon(IconName::Check).icon_color(Color::Success) this.icon(IconName::Check).icon_color(Color::Success)
@ -1628,6 +1628,7 @@ impl AcpThreadView {
Button::new( Button::new(
SharedString::from(format!("stop-terminal-{}", terminal.entity_id())), SharedString::from(format!("stop-terminal-{}", terminal.entity_id())),
"Stop", "Stop",
cx,
) )
.icon(IconName::Stop) .icon(IconName::Stop)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
@ -1932,7 +1933,7 @@ impl AcpThreadView {
{ {
let upgrade_message = upgrade_message.clone(); let upgrade_message = upgrade_message.clone();
let upgrade_command = upgrade_command.clone(); let upgrade_command = upgrade_command.clone();
container = container.child(Button::new("upgrade", upgrade_message).on_click( container = container.child(Button::new("upgrade", upgrade_message, cx).on_click(
cx.listener(move |this, _, window, cx| { cx.listener(move |this, _, window, cx| {
this.workspace this.workspace
.update(cx, |workspace, cx| { .update(cx, |workspace, cx| {
@ -2264,7 +2265,7 @@ impl AcpThreadView {
) )
.child(Divider::vertical().color(DividerColor::Border)) .child(Divider::vertical().color(DividerColor::Border))
.child( .child(
Button::new("reject-all-changes", "Reject All") Button::new("reject-all-changes", "Reject All", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(pending_edits) .disabled(pending_edits)
.when(pending_edits, |this| { .when(pending_edits, |this| {
@ -2289,7 +2290,7 @@ impl AcpThreadView {
}), }),
) )
.child( .child(
Button::new("keep-all-changes", "Keep All") Button::new("keep-all-changes", "Keep All", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(pending_edits) .disabled(pending_edits)
.when(pending_edits, |this| { .when(pending_edits, |this| {
@ -2395,7 +2396,7 @@ impl AcpThreadView {
.gap_1() .gap_1()
.visible_on_hover("edited-code") .visible_on_hover("edited-code")
.child( .child(
Button::new("review", "Review") Button::new("review", "Review", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click({ .on_click({
let buffer = buffer.clone(); let buffer = buffer.clone();
@ -2406,7 +2407,7 @@ impl AcpThreadView {
) )
.child(Divider::vertical().color(DividerColor::BorderVariant)) .child(Divider::vertical().color(DividerColor::BorderVariant))
.child( .child(
Button::new("reject-file", "Reject") Button::new("reject-file", "Reject", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(pending_edits) .disabled(pending_edits)
.on_click({ .on_click({
@ -2426,7 +2427,7 @@ impl AcpThreadView {
}), }),
) )
.child( .child(
Button::new("keep-file", "Keep") Button::new("keep-file", "Keep", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(pending_edits) .disabled(pending_edits)
.on_click({ .on_click({
@ -3114,6 +3115,7 @@ impl Render for AcpThreadView {
Button::new( Button::new(
SharedString::from(method.id.0.clone()), SharedString::from(method.id.0.clone()),
method.name.clone(), method.name.clone(),
cx,
) )
.on_click({ .on_click({
let method_id = method.id.clone(); let method_id = method.id.clone();

View file

@ -2281,7 +2281,7 @@ impl ActiveThread {
} }
let restore_checkpoint_button = let restore_checkpoint_button =
Button::new(("restore-checkpoint", ix), "Restore Checkpoint") Button::new(("restore-checkpoint", ix), "Restore Checkpoint", cx)
.icon(if error.is_some() { .icon(if error.is_some() {
IconName::XCircle IconName::XCircle
} else { } else {
@ -2371,7 +2371,7 @@ impl ActiveThread {
.gap_1() .gap_1()
.justify_end() .justify_end()
.child( .child(
Button::new("dismiss-feedback-message", "Cancel") Button::new("dismiss-feedback-message", "Cancel", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.key_binding( .key_binding(
KeyBinding::for_action_in( KeyBinding::for_action_in(
@ -2394,6 +2394,7 @@ impl ActiveThread {
Button::new( Button::new(
"submit-feedback-message", "submit-feedback-message",
"Share Feedback", "Share Feedback",
cx,
) )
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
@ -3217,6 +3218,7 @@ impl ActiveThread {
Button::new( Button::new(
"always-allow-tool-action", "always-allow-tool-action",
"Always Allow", "Always Allow",
cx,
) )
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::CheckDouble) .icon(IconName::CheckDouble)
@ -3254,7 +3256,7 @@ impl ActiveThread {
}) })
.child({ .child({
let tool_id = tool_use.id.clone(); let tool_id = tool_use.id.clone();
Button::new("allow-tool-action", "Allow") Button::new("allow-tool-action", "Allow", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::Check) .icon(IconName::Check)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
@ -3274,7 +3276,7 @@ impl ActiveThread {
.child({ .child({
let tool_id = tool_use.id.clone(); let tool_id = tool_use.id.clone();
let tool_name: Arc<str> = tool_use.name.into(); let tool_name: Arc<str> = tool_use.name.into();
Button::new("deny-tool", "Deny") Button::new("deny-tool", "Deny", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::Close) .icon(IconName::Close)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)

View file

@ -281,6 +281,7 @@ impl AgentConfiguration {
Button::new( Button::new(
SharedString::from(format!("new-thread-{provider_id}")), SharedString::from(format!("new-thread-{provider_id}")),
"Start New Thread", "Start New Thread",
cx,
) )
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon(IconName::Plus) .icon(IconName::Plus)
@ -339,7 +340,7 @@ impl AgentConfiguration {
.child( .child(
PopoverMenu::new("add-provider-popover") PopoverMenu::new("add-provider-popover")
.trigger( .trigger(
Button::new("add-provider", "Add Provider") Button::new("add-provider", "Add Provider", cx)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon(IconName::Plus) .icon(IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -552,7 +553,7 @@ impl AgentConfiguration {
.gap_2() .gap_2()
.child( .child(
h_flex().w_full().child( h_flex().w_full().child(
Button::new("add-context-server", "Add Custom Server") Button::new("add-context-server", "Add Custom Server", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface) .layer(ElevationIndex::ModalSurface)
.full_width() .full_width()
@ -569,6 +570,7 @@ impl AgentConfiguration {
Button::new( Button::new(
"install-context-server-extensions", "install-context-server-extensions",
"Install MCP Extensions", "Install MCP Extensions",
cx,
) )
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface) .layer(ElevationIndex::ModalSurface)

View file

@ -281,7 +281,7 @@ impl AddLlmProviderModal {
.justify_between() .justify_between()
.child(Label::new("Models").size(LabelSize::Small)) .child(Label::new("Models").size(LabelSize::Small))
.child( .child(
Button::new("add-model", "Add Model") Button::new("add-model", "Add Model", cx)
.icon(IconName::Plus) .icon(IconName::Plus)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
@ -324,7 +324,7 @@ impl AddLlmProviderModal {
.child(model.max_tokens.clone()) .child(model.max_tokens.clone())
.when(has_more_than_one_model, |this| { .when(has_more_than_one_model, |this| {
this.child( this.child(
Button::new(("remove-model", ix), "Remove Model") Button::new(("remove-model", ix), "Remove Model", cx)
.icon(IconName::Trash) .icon(IconName::Trash)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
@ -400,7 +400,7 @@ impl Render for AddLlmProviderModal {
h_flex() h_flex()
.gap_1() .gap_1()
.child( .child(
Button::new("cancel", "Cancel") Button::new("cancel", "Cancel", cx)
.key_binding( .key_binding(
KeyBinding::for_action_in( KeyBinding::for_action_in(
&menu::Cancel, &menu::Cancel,
@ -415,7 +415,7 @@ impl Render for AddLlmProviderModal {
})), })),
) )
.child( .child(
Button::new("save-server", "Save Provider") Button::new("save-server", "Save Provider", cx)
.key_binding( .key_binding(
KeyBinding::for_action_in( KeyBinding::for_action_in(
&menu::Confirm, &menu::Confirm,

View file

@ -564,7 +564,7 @@ impl ConfigureContextServerModal {
} = &self.source } = &self.source
{ {
Some( Some(
Button::new("open-repository", "Open Repository") Button::new("open-repository", "Open Repository", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -600,6 +600,7 @@ impl ConfigureContextServerModal {
} else { } else {
"Dismiss" "Dismiss"
}, },
cx,
) )
.key_binding( .key_binding(
KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx) KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx)
@ -617,6 +618,7 @@ impl ConfigureContextServerModal {
} else { } else {
"Configure Server" "Configure Server"
}, },
cx,
) )
.disabled(is_connecting) .disabled(is_connecting)
.key_binding( .key_binding(

View file

@ -726,7 +726,7 @@ impl Render for AgentDiffPane {
.gap_2() .gap_2()
.child("No changes to review") .child("No changes to review")
.child( .child(
Button::new("continue-iterating", "Continue Iterating") Button::new("continue-iterating", "Continue Iterating", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.icon(IconName::ForwardArrow) .icon(IconName::ForwardArrow)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
@ -806,7 +806,7 @@ fn render_diff_hunk_controls(
.block_mouse_except_scroll() .block_mouse_except_scroll()
.shadow_md() .shadow_md()
.children(vec![ .children(vec![
Button::new(("reject", row as u64), "Reject") Button::new(("reject", row as u64), "Reject", cx)
.disabled(is_created_file) .disabled(is_created_file)
.key_binding( .key_binding(
KeyBinding::for_action_in( KeyBinding::for_action_in(
@ -834,7 +834,7 @@ fn render_diff_hunk_controls(
}) })
} }
}), }),
Button::new(("keep", row as u64), "Keep") Button::new(("keep", row as u64), "Keep", cx)
.key_binding( .key_binding(
KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx) KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx)
.map(|kb| kb.size(rems_from_px(12.))), .map(|kb| kb.size(rems_from_px(12.))),
@ -1147,7 +1147,7 @@ impl Render for AgentDiffToolbar {
h_flex() h_flex()
.gap_0p5() .gap_0p5()
.child( .child(
Button::new("reject-all", "Reject All") Button::new("reject-all", "Reject All", cx)
.key_binding({ .key_binding({
KeyBinding::for_action_in( KeyBinding::for_action_in(
&RejectAll, &RejectAll,
@ -1162,7 +1162,7 @@ impl Render for AgentDiffToolbar {
})), })),
) )
.child( .child(
Button::new("keep-all", "Keep All") Button::new("keep-all", "Keep All", cx)
.key_binding({ .key_binding({
KeyBinding::for_action_in( KeyBinding::for_action_in(
&KeepAll, &KeepAll,
@ -1242,7 +1242,7 @@ impl Render for AgentDiffToolbar {
.child( .child(
h_group_sm() h_group_sm()
.child( .child(
Button::new("reject-all", "Reject All") Button::new("reject-all", "Reject All", cx)
.key_binding({ .key_binding({
KeyBinding::for_action_in( KeyBinding::for_action_in(
&RejectAll, &RejectAll,
@ -1257,7 +1257,7 @@ impl Render for AgentDiffToolbar {
})), })),
) )
.child( .child(
Button::new("keep-all", "Keep All") Button::new("keep-all", "Keep All", cx)
.key_binding({ .key_binding({
KeyBinding::for_action_in( KeyBinding::for_action_in(
&KeepAll, &KeepAll,

View file

@ -2444,7 +2444,7 @@ impl AgentPanel {
.gap_1() .gap_1()
.max_w_48() .max_w_48()
.child( .child(
Button::new("context", "Add Context") Button::new("context", "Add Context", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::FileCode) .icon(IconName::FileCode)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
@ -2465,7 +2465,7 @@ impl AgentPanel {
}), }),
) )
.child( .child(
Button::new("mode", "Switch Model") Button::new("mode", "Switch Model", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::DatabaseZap) .icon(IconName::DatabaseZap)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
@ -2486,7 +2486,7 @@ impl AgentPanel {
}), }),
) )
.child( .child(
Button::new("settings", "View Settings") Button::new("settings", "View Settings", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::Settings) .icon(IconName::Settings)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
@ -2529,7 +2529,7 @@ impl AgentPanel {
self.render_empty_state_section_header( self.render_empty_state_section_header(
"Recent", "Recent",
Some( Some(
Button::new("view-history", "View All") Button::new("view-history", "View All", cx)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.key_binding( .key_binding(
@ -2727,7 +2727,7 @@ impl AgentPanel {
.severity(ui::Severity::Warning) .severity(ui::Severity::Warning)
.child(Label::new(configuration_error.to_string())) .child(Label::new(configuration_error.to_string()))
.action_slot( .action_slot(
Button::new("settings", "Configure Provider") Button::new("settings", "Configure Provider", cx)
.style(ButtonStyle::Tinted(ui::TintColor::Warning)) .style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.key_binding( .key_binding(
@ -2784,7 +2784,7 @@ impl AgentPanel {
h_flex() h_flex()
.gap_1() .gap_1()
.child( .child(
Button::new("continue-conversation", "Continue") Button::new("continue-conversation", "Continue", cx)
.layer(ElevationIndex::ModalSurface) .layer(ElevationIndex::ModalSurface)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.key_binding( .key_binding(
@ -2802,7 +2802,7 @@ impl AgentPanel {
) )
.when(model.supports_burn_mode(), |this| { .when(model.supports_burn_mode(), |this| {
this.child( this.child(
Button::new("continue-burn-mode", "Continue with Burn Mode") Button::new("continue-burn-mode", "Continue with Burn Mode", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.layer(ElevationIndex::ModalSurface) .layer(ElevationIndex::ModalSurface)
@ -2873,7 +2873,7 @@ impl AgentPanel {
thread: &Entity<ActiveThread>, thread: &Entity<ActiveThread>,
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> impl IntoElement { ) -> impl IntoElement {
Button::new("upgrade", "Upgrade") Button::new("upgrade", "Upgrade", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(cx.listener({ .on_click(cx.listener({
@ -2965,7 +2965,7 @@ impl AgentPanel {
.size(IconSize::Small) .size(IconSize::Small)
.color(Color::Error); .color(Color::Error);
let retry_button = Button::new("retry", "Retry") let retry_button = Button::new("retry", "Retry", cx)
.icon(IconName::RotateCw) .icon(IconName::RotateCw)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -3009,7 +3009,7 @@ impl AgentPanel {
.size(IconSize::Small) .size(IconSize::Small)
.color(Color::Error); .color(Color::Error);
let retry_button = Button::new("retry", "Retry") let retry_button = Button::new("retry", "Retry", cx)
.icon(IconName::RotateCw) .icon(IconName::RotateCw)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -3034,22 +3034,26 @@ impl AgentPanel {
.primary_action(retry_button); .primary_action(retry_button);
if can_enable_burn_mode { if can_enable_burn_mode {
let burn_mode_button = Button::new("enable_burn_retry", "Enable Burn Mode and Retry") let burn_mode_button =
.icon(IconName::ZedBurnMode) Button::new("enable_burn_retry", "Enable Burn Mode and Retry", cx)
.icon_position(IconPosition::Start) .icon(IconName::ZedBurnMode)
.icon_size(IconSize::Small) .icon_position(IconPosition::Start)
.label_size(LabelSize::Small) .icon_size(IconSize::Small)
.on_click({ .label_size(LabelSize::Small)
let thread = thread.clone(); .on_click({
move |_, window, cx| { let thread = thread.clone();
thread.update(cx, |thread, cx| { move |_, window, cx| {
thread.clear_last_error(); thread.update(cx, |thread, cx| {
thread.thread().update(cx, |thread, cx| { thread.clear_last_error();
thread.enable_burn_mode_and_retry(Some(window.window_handle()), cx); thread.thread().update(cx, |thread, cx| {
thread.enable_burn_mode_and_retry(
Some(window.window_handle()),
cx,
);
});
}); });
}); }
} });
});
callout = callout.secondary_action(burn_mode_button); callout = callout.secondary_action(burn_mode_button);
} }

View file

@ -478,7 +478,7 @@ impl<T: 'static> PromptEditor<T> {
match codegen_status { match codegen_status {
CodegenStatus::Idle => { CodegenStatus::Idle => {
vec![ vec![
Button::new("start", mode.start_label()) Button::new("start", mode.start_label(), cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::Return) .icon(IconName::Return)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
@ -745,11 +745,11 @@ impl<T: 'static> PromptEditor<T> {
h_flex() h_flex()
.gap_2() .gap_2()
.child( .child(
Button::new("dismiss", "Dismiss") Button::new("dismiss", "Dismiss", cx)
.style(ButtonStyle::Transparent) .style(ButtonStyle::Transparent)
.on_click(cx.listener(Self::toggle_rate_limit_notice)), .on_click(cx.listener(Self::toggle_rate_limit_notice)),
) )
.child(Button::new("more-info", "More Info").on_click( .child(Button::new("more-info", "More Info", cx).on_click(
|_event, window, cx| { |_event, window, cx| {
window.dispatch_action( window.dispatch_action(
Box::new(zed_actions::OpenAccountSettings), Box::new(zed_actions::OpenAccountSettings),

View file

@ -548,7 +548,7 @@ impl PickerDelegate for LanguageModelPickerDelegate {
.justify_between() .justify_between()
.when(cx.has_flag::<ZedProFeatureFlag>(), |this| { .when(cx.has_flag::<ZedProFeatureFlag>(), |this| {
this.child(match plan { this.child(match plan {
Plan::ZedPro => Button::new("zed-pro", "Zed Pro") Plan::ZedPro => Button::new("zed-pro", "Zed Pro", cx)
.icon(IconName::ZedAssistant) .icon(IconName::ZedAssistant)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted)
@ -564,12 +564,13 @@ impl PickerDelegate for LanguageModelPickerDelegate {
} else { } else {
"Try Pro" "Try Pro"
}, },
cx,
) )
.on_click(|_, _, cx| cx.open_url(TRY_ZED_PRO_URL)), .on_click(|_, _, cx| cx.open_url(TRY_ZED_PRO_URL)),
}) })
}) })
.child( .child(
Button::new("configure", "Configure") Button::new("configure", "Configure", cx)
.icon(IconName::Settings) .icon(IconName::Settings)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted)

View file

@ -1074,7 +1074,7 @@ impl MessageEditor {
) )
.child(Divider::vertical().color(DividerColor::Border)) .child(Divider::vertical().color(DividerColor::Border))
.child( .child(
Button::new("reject-all-changes", "Reject All") Button::new("reject-all-changes", "Reject All", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(pending_edits) .disabled(pending_edits)
.when(pending_edits, |this| { .when(pending_edits, |this| {
@ -1094,7 +1094,7 @@ impl MessageEditor {
})), })),
) )
.child( .child(
Button::new("accept-all-changes", "Accept All") Button::new("accept-all-changes", "Accept All", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(pending_edits) .disabled(pending_edits)
.when(pending_edits, |this| { .when(pending_edits, |this| {
@ -1204,7 +1204,7 @@ impl MessageEditor {
.gap_1() .gap_1()
.visible_on_hover("edited-code") .visible_on_hover("edited-code")
.child( .child(
Button::new("review", "Review") Button::new("review", "Review", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click({ .on_click({
let buffer = buffer.clone(); let buffer = buffer.clone();
@ -1221,7 +1221,7 @@ impl MessageEditor {
Divider::vertical().color(DividerColor::BorderVariant), Divider::vertical().color(DividerColor::BorderVariant),
) )
.child( .child(
Button::new("reject-file", "Reject") Button::new("reject-file", "Reject", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(pending_edits) .disabled(pending_edits)
.on_click({ .on_click({
@ -1236,7 +1236,7 @@ impl MessageEditor {
}), }),
) )
.child( .child(
Button::new("accept-file", "Accept") Button::new("accept-file", "Accept", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(pending_edits) .disabled(pending_edits)
.on_click({ .on_click({
@ -1332,7 +1332,7 @@ impl MessageEditor {
.title(title) .title(title)
.description(description) .description(description)
.primary_action( .primary_action(
Button::new("start-new-thread", "Start New Thread") Button::new("start-new-thread", "Start New Thread", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
let from_thread_id = Some(this.thread.read(cx).id().clone()); let from_thread_id = Some(this.thread.read(cx).id().clone());

View file

@ -167,7 +167,7 @@ impl Render for ProfileSelector {
if configured_model.model.supports_tools() { if configured_model.model.supports_tools() {
let this = cx.entity().clone(); let this = cx.entity().clone();
let focus_handle = self.focus_handle.clone(); let focus_handle = self.focus_handle.clone();
let trigger_button = Button::new("profile-selector-model", selected_profile) let trigger_button = Button::new("profile-selector-model", selected_profile, cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.color(Color::Muted) .color(Color::Muted)
.icon(IconName::ChevronDown) .icon(IconName::ChevronDown)
@ -201,7 +201,7 @@ impl Render for ProfileSelector {
}) })
.into_any_element() .into_any_element()
} else { } else {
Button::new("tools-not-supported-button", "Tools Unsupported") Button::new("tools-not-supported-button", "Tools Unsupported", cx)
.disabled(true) .disabled(true)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.color(Color::Muted) .color(Color::Muted)

View file

@ -1200,7 +1200,7 @@ impl TextThreadEditor {
}) })
.children(match &message.status { .children(match &message.status {
MessageStatus::Error(error) => Some( MessageStatus::Error(error) => Some(
Button::new("show-error", "Error") Button::new("show-error", "Error", cx)
.color(Color::Error) .color(Color::Error)
.selected_label_color(Color::Error) .selected_label_color(Color::Error)
.selected_icon_color(Color::Error) .selected_icon_color(Color::Error)
@ -1920,7 +1920,7 @@ impl TextThreadEditor {
None => (ButtonStyle::Filled, None), None => (ButtonStyle::Filled, None),
}; };
Button::new("send_button", "Send") Button::new("send_button", "Send", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.disabled(self.sending_disabled(cx)) .disabled(self.sending_disabled(cx))
.style(style) .style(style)
@ -2124,14 +2124,16 @@ impl TextThreadEditor {
h_flex() h_flex()
.justify_end() .justify_end()
.mt_1() .mt_1()
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener( .child(
|this, _, _window, cx| { Button::new("subscribe", "Subscribe", cx).on_click(cx.listener(
this.last_error = None; |this, _, _window, cx| {
cx.open_url(&zed_urls::account_url(cx)); this.last_error = None;
cx.notify(); cx.open_url(&zed_urls::account_url(cx));
}, cx.notify();
))) },
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener( )),
)
.child(Button::new("dismiss", "Dismiss", cx).on_click(cx.listener(
|this, _, _window, cx| { |this, _, _window, cx| {
this.last_error = None; this.last_error = None;
cx.notify(); cx.notify();
@ -2165,17 +2167,14 @@ impl TextThreadEditor {
.overflow_y_scroll() .overflow_y_scroll()
.child(Label::new(error_message.clone())), .child(Label::new(error_message.clone())),
) )
.child( .child(h_flex().justify_end().mt_1().child(
h_flex() Button::new("dismiss", "Dismiss", cx).on_click(cx.listener(
.justify_end() |this, _, _window, cx| {
.mt_1() this.last_error = None;
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener( cx.notify();
|this, _, _window, cx| { },
this.last_error = None; )),
cx.notify(); ))
},
))),
)
.into_any() .into_any()
} }
} }

View file

@ -171,7 +171,7 @@ impl Render for AgentNotification {
.gap_1() .gap_1()
.items_center() .items_center()
.child( .child(
Button::new("open", "View Panel") Button::new("open", "View Panel", cx)
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.full_width() .full_width()
.on_click({ .on_click({
@ -180,11 +180,15 @@ impl Render for AgentNotification {
}) })
}), }),
) )
.child(Button::new("dismiss", "Dismiss").full_width().on_click({ .child(
cx.listener(move |_, _event, _, cx| { Button::new("dismiss", "Dismiss", cx)
cx.emit(AgentNotificationEvent::Dismissed); .full_width()
}) .on_click({
})), cx.listener(move |_, _event, _, cx| {
cx.emit(AgentNotificationEvent::Dismissed);
})
}),
),
) )
} }
} }

View file

@ -35,7 +35,7 @@ impl RenderOnce for EndTrialUpsell {
) )
.child(plan_definitions.pro_plan(false)) .child(plan_definitions.pro_plan(false))
.child( .child(
Button::new("cta-button", "Upgrade to Zed Pro") Button::new("cta-button", "Upgrade to Zed Pro", cx)
.full_width() .full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(move |_, _window, cx| { .on_click(move |_, _window, cx| {

View file

@ -147,13 +147,13 @@ impl Render for AgentOnboardingModal {
)), )),
)); ));
let open_panel_button = Button::new("open-panel", "Get Started with the Agent Panel") let open_panel_button = Button::new("open-panel", "Get Started with the Agent Panel", cx)
.icon_size(IconSize::Indicator) .icon_size(IconSize::Indicator)
.style(ButtonStyle::Tinted(TintColor::Accent)) .style(ButtonStyle::Tinted(TintColor::Accent))
.full_width() .full_width()
.on_click(cx.listener(Self::open_panel)); .on_click(cx.listener(Self::open_panel));
let blog_post_button = Button::new("view-blog", "Check out the Blog Post") let blog_post_button = Button::new("view-blog", "Check out the Blog Post", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Indicator) .icon_size(IconSize::Indicator)
.icon_color(Color::Muted) .icon_color(Color::Muted)

View file

@ -99,7 +99,7 @@ impl RenderOnce for UsageCallout {
.title(title) .title(title)
.description(message) .description(message)
.primary_action( .primary_action(
Button::new("upgrade", button_text) Button::new("upgrade", button_text, cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(move |_, _, cx| { .on_click(move |_, _, cx| {
cx.open_url(&url); cx.open_url(&url);

View file

@ -130,7 +130,7 @@ impl RenderOnce for ApiKeysWithoutProviders {
"Add your own keys to use AI without signing in.", "Add your own keys to use AI without signing in.",
))) )))
.child( .child(
Button::new("configure-providers", "Configure Providers") Button::new("configure-providers", "Configure Providers", cx)
.full_width() .full_width()
.style(ButtonStyle::Outlined) .style(ButtonStyle::Outlined)
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {

View file

@ -94,7 +94,7 @@ impl ZedAiOnboarding {
self self
} }
fn render_accept_terms_of_service(&self) -> AnyElement { fn render_accept_terms_of_service(&self, cx: &mut App) -> AnyElement {
v_flex() v_flex()
.gap_1() .gap_1()
.w_full() .w_full()
@ -105,7 +105,7 @@ impl ZedAiOnboarding {
.mb_2(), .mb_2(),
) )
.child( .child(
Button::new("terms_of_service", "Review Terms of Service") Button::new("terms_of_service", "Review Terms of Service", cx)
.full_width() .full_width()
.style(ButtonStyle::Outlined) .style(ButtonStyle::Outlined)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
@ -117,7 +117,7 @@ impl ZedAiOnboarding {
}), }),
) )
.child( .child(
Button::new("accept_terms", "Accept") Button::new("accept_terms", "Accept", cx)
.full_width() .full_width()
.style(ButtonStyle::Tinted(TintColor::Accent)) .style(ButtonStyle::Tinted(TintColor::Accent))
.on_click({ .on_click({
@ -130,7 +130,7 @@ impl ZedAiOnboarding {
.into_any_element() .into_any_element()
} }
fn render_sign_in_disclaimer(&self, _cx: &mut App) -> AnyElement { fn render_sign_in_disclaimer(&self, cx: &mut App) -> AnyElement {
let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn); let signing_in = matches!(self.sign_in_status, SignInStatus::SigningIn);
let plan_definitions = PlanDefinitions; let plan_definitions = PlanDefinitions;
@ -144,7 +144,7 @@ impl ZedAiOnboarding {
) )
.child(plan_definitions.pro_plan(false)) .child(plan_definitions.pro_plan(false))
.child( .child(
Button::new("sign_in", "Try Zed Pro for Free") Button::new("sign_in", "Try Zed Pro for Free", cx)
.disabled(signing_in) .disabled(signing_in)
.full_width() .full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
@ -187,7 +187,7 @@ impl ZedAiOnboarding {
) )
.child(plan_definitions.pro_plan(true)) .child(plan_definitions.pro_plan(true))
.child( .child(
Button::new("pro", "Get Started") Button::new("pro", "Get Started", cx)
.full_width() .full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(move |_, _window, cx| { .on_click(move |_, _window, cx| {
@ -268,7 +268,7 @@ impl ZedAiOnboarding {
) )
.child(plan_definitions.pro_trial(true)) .child(plan_definitions.pro_trial(true))
.child( .child(
Button::new("pro", "Start Free Trial") Button::new("pro", "Start Free Trial", cx)
.full_width() .full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(move |_, _window, cx| { .on_click(move |_, _window, cx| {
@ -320,7 +320,7 @@ impl ZedAiOnboarding {
.into_any_element() .into_any_element()
} }
fn render_pro_plan_state(&self, _cx: &mut App) -> AnyElement { fn render_pro_plan_state(&self, cx: &mut App) -> AnyElement {
let plan_definitions = PlanDefinitions; let plan_definitions = PlanDefinitions;
v_flex() v_flex()
@ -333,7 +333,7 @@ impl ZedAiOnboarding {
) )
.child(plan_definitions.pro_plan(false)) .child(plan_definitions.pro_plan(false))
.child( .child(
Button::new("pro", "Continue with Zed Pro") Button::new("pro", "Continue with Zed Pro", cx)
.full_width() .full_width()
.style(ButtonStyle::Outlined) .style(ButtonStyle::Outlined)
.on_click({ .on_click({
@ -358,7 +358,7 @@ impl RenderOnce for ZedAiOnboarding {
Some(Plan::ZedPro) => self.render_pro_plan_state(cx), Some(Plan::ZedPro) => self.render_pro_plan_state(cx),
} }
} else { } else {
self.render_accept_terms_of_service() self.render_accept_terms_of_service(cx)
} }
} else { } else {
self.render_sign_in_disclaimer(cx) self.render_sign_in_disclaimer(cx)

View file

@ -186,7 +186,7 @@ impl RenderOnce for AiUpsellCard {
) )
.child(plan_definitions.pro_plan(true)) .child(plan_definitions.pro_plan(true))
.child( .child(
Button::new("pro", "Get Started") Button::new("pro", "Get Started", cx)
.full_width() .full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(move |_, _window, cx| { .on_click(move |_, _window, cx| {
@ -209,19 +209,25 @@ impl RenderOnce for AiUpsellCard {
.child( .child(
footer_container footer_container
.child( .child(
Button::new("start_trial", "Start 14-day Free Pro Trial") Button::new(
.full_width() "start_trial",
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) "Start 14-day Free Pro Trial",
.when_some(self.tab_index, |this, tab_index| { cx,
this.tab_index(tab_index) )
}) .full_width()
.on_click(move |_, _window, cx| { .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.when_some(self.tab_index, |this, tab_index| {
this.tab_index(tab_index)
})
.on_click(
move |_, _window, cx| {
telemetry::event!( telemetry::event!(
"Start Trial Clicked", "Start Trial Clicked",
state = "post-sign-in" state = "post-sign-in"
); );
cx.open_url(&zed_urls::start_trial_url(cx)) cx.open_url(&zed_urls::start_trial_url(cx))
}), },
),
) )
.child( .child(
Label::new("No credit card required") Label::new("No credit card required")
@ -261,7 +267,7 @@ impl RenderOnce for AiUpsellCard {
) )
.child(plans_section) .child(plans_section)
.child( .child(
Button::new("sign_in", "Sign In") Button::new("sign_in", "Sign In", cx)
.full_width() .full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ButtonStyle::Tinted(ui::TintColor::Accent))
.when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index)) .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))

View file

@ -50,6 +50,7 @@ impl Render for EditPredictionOnboarding {
} else { } else {
"Configure Copilot" "Configure Copilot"
}, },
cx,
) )
.full_width() .full_width()
.style(ButtonStyle::Outlined) .style(ButtonStyle::Outlined)

View file

@ -256,7 +256,7 @@ impl ToolCard for FindPathToolCard {
let workspace_clone = workspace.clone(); let workspace_clone = workspace.clone();
let button_label = path.to_string_lossy().to_string(); let button_label = path.to_string_lossy().to_string();
Button::new(("path", index), button_label) Button::new(("path", index), button_label, cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_position(IconPosition::End) .icon_position(IconPosition::End)

View file

@ -174,7 +174,7 @@ impl ToolCard for WebSearchToolCard {
let title = result.title.clone(); let title = result.title.clone();
let url = SharedString::from(result.url.clone()); let url = SharedString::from(result.url.clone());
Button::new(("result", index), title) Button::new(("result", index), title, cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.color(Color::Muted) .color(Color::Muted)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)

View file

@ -987,7 +987,7 @@ impl Render for ChatPanel {
) )
.child( .child(
div().pt_1().w_full().items_center().child( div().pt_1().w_full().items_center().child(
Button::new("toggle-collab", "Open") Button::new("toggle-collab", "Open", cx)
.full_width() .full_width()
.key_binding(KeyBinding::for_action( .key_binding(KeyBinding::for_action(
&collab_panel::ToggleFocus, &collab_panel::ToggleFocus,

View file

@ -2307,7 +2307,7 @@ impl CollabPanel {
v_flex() v_flex()
.gap_2() .gap_2()
.child( .child(
Button::new("sign_in", "Sign in") Button::new("sign_in", "Sign in", cx)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.icon(IconName::Github) .icon(IconName::Github)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)

View file

@ -177,7 +177,7 @@ impl Render for ChannelModal {
)) ))
.children( .children(
Some( Some(
Button::new("copy-link", "Copy Link") Button::new("copy-link", "Copy Link", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(cx.listener(move |this, _, _, cx| { .on_click(cx.listener(move |this, _, _, cx| {
if let Some(channel) = this if let Some(channel) = this

View file

@ -319,20 +319,22 @@ impl NotificationPanel {
h_flex() h_flex()
.flex_grow() .flex_grow()
.justify_end() .justify_end()
.child(Button::new("decline", "Decline").on_click({ .child(Button::new("decline", "Decline", cx).on_click(
let notification = notification.clone(); {
let entity = cx.entity().clone(); let notification = notification.clone();
move |_, _, cx| { let entity = cx.entity().clone();
entity.update(cx, |this, cx| { move |_, _, cx| {
this.respond_to_notification( entity.update(cx, |this, cx| {
notification.clone(), this.respond_to_notification(
false, notification.clone(),
cx, false,
) cx,
}); )
} });
})) }
.child(Button::new("accept", "Accept").on_click({ },
))
.child(Button::new("accept", "Accept", cx).on_click({
let notification = notification.clone(); let notification = notification.clone();
let entity = cx.entity().clone(); let entity = cx.entity().clone();
move |_, _, cx| { move |_, _, cx| {
@ -631,7 +633,7 @@ impl Render for NotificationPanel {
.gap_2() .gap_2()
.p_4() .p_4()
.child( .child(
Button::new("connect_prompt_button", "Connect") Button::new("connect_prompt_button", "Connect", cx)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.icon(IconName::Github) .icon(IconName::Github)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)

View file

@ -117,11 +117,11 @@ impl Render for IncomingCallNotification {
div().size_full().font(ui_font).child( div().size_full().font(ui_font).child(
CollabNotification::new( CollabNotification::new(
self.state.call.calling_user.avatar_uri.clone(), self.state.call.calling_user.avatar_uri.clone(),
Button::new("accept", "Accept").on_click({ Button::new("accept", "Accept", cx).on_click({
let state = self.state.clone(); let state = self.state.clone();
move |_, _, cx| state.respond(true, cx) move |_, _, cx| state.respond(true, cx)
}), }),
Button::new("decline", "Decline").on_click({ Button::new("decline", "Decline", cx).on_click({
let state = self.state.clone(); let state = self.state.clone();
move |_, _, cx| state.respond(false, cx) move |_, _, cx| state.respond(false, cx)
}), }),

View file

@ -126,10 +126,12 @@ impl Render for ProjectSharedNotification {
div().size_full().font(ui_font).child( div().size_full().font(ui_font).child(
CollabNotification::new( CollabNotification::new(
self.owner.avatar_uri.clone(), self.owner.avatar_uri.clone(),
Button::new("open", "Open").on_click(cx.listener(move |this, _event, _, cx| { Button::new("open", "Open", cx).on_click(cx.listener(
this.join(cx); move |this, _event, _, cx| {
})), this.join(cx);
Button::new("dismiss", "Dismiss").on_click(cx.listener( },
)),
Button::new("dismiss", "Dismiss", cx).on_click(cx.listener(
move |this, _event, _, cx| { move |this, _event, _, cx| {
this.dismiss(cx); this.dismiss(cx);
}, },

View file

@ -18,8 +18,8 @@ impl Render for CollabNotificationStory {
window_container(400., 72.).child( window_container(400., 72.).child(
CollabNotification::new( CollabNotification::new(
"https://avatars.githubusercontent.com/u/1486634?v=4", "https://avatars.githubusercontent.com/u/1486634?v=4",
Button::new("accept", "Accept"), Button::new("accept", "Accept", cx),
Button::new("decline", "Decline"), Button::new("decline", "Decline", cx),
) )
.child( .child(
v_flex() v_flex()
@ -35,8 +35,8 @@ impl Render for CollabNotificationStory {
window_container(400., 72.).child( window_container(400., 72.).child(
CollabNotification::new( CollabNotification::new(
"https://avatars.githubusercontent.com/u/1714999?v=4", "https://avatars.githubusercontent.com/u/1714999?v=4",
Button::new("open", "Open"), Button::new("open", "Open", cx),
Button::new("dismiss", "Dismiss"), Button::new("dismiss", "Dismiss", cx),
) )
.child(Label::new("iamnbutler")) .child(Label::new("iamnbutler"))
.child(Label::new("is sharing a project in Zed:")) .child(Label::new("is sharing a project in Zed:"))

View file

@ -264,7 +264,7 @@ impl CopilotCodeVerification {
.size(ui::LabelSize::Small), .size(ui::LabelSize::Small),
) )
.child( .child(
Button::new("connect-button", connect_button_label) Button::new("connect-button", connect_button_label, cx)
.on_click({ .on_click({
let verification_uri = data.verification_uri.clone(); let verification_uri = data.verification_uri.clone();
cx.listener(move |this, _, _window, cx| { cx.listener(move |this, _, _window, cx| {
@ -276,7 +276,7 @@ impl CopilotCodeVerification {
.style(ButtonStyle::Filled), .style(ButtonStyle::Filled),
) )
.child( .child(
Button::new("copilot-enable-cancel-button", "Cancel") Button::new("copilot-enable-cancel-button", "Cancel", cx)
.full_width() .full_width()
.on_click(cx.listener(|_, _, _, cx| { .on_click(cx.listener(|_, _, _, cx| {
cx.emit(DismissEvent); cx.emit(DismissEvent);
@ -292,7 +292,7 @@ impl CopilotCodeVerification {
"You can update your settings or sign out from the Copilot menu in the status bar.", "You can update your settings or sign out from the Copilot menu in the status bar.",
)) ))
.child( .child(
Button::new("copilot-enabled-done-button", "Done") Button::new("copilot-enabled-done-button", "Done", cx)
.full_width() .full_width()
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))), .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
) )
@ -306,12 +306,12 @@ impl CopilotCodeVerification {
"You can enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.", "You can enable Copilot by connecting your existing license once you have subscribed or renewed your subscription.",
).color(Color::Warning)) ).color(Color::Warning))
.child( .child(
Button::new("copilot-subscribe-button", "Subscribe on GitHub") Button::new("copilot-subscribe-button", "Subscribe on GitHub", cx)
.full_width() .full_width()
.on_click(|_, _, cx| cx.open_url(COPILOT_SIGN_UP_URL)), .on_click(|_, _, cx| cx.open_url(COPILOT_SIGN_UP_URL)),
) )
.child( .child(
Button::new("copilot-subscribe-cancel-button", "Cancel") Button::new("copilot-subscribe-cancel-button", "Cancel", cx)
.full_width() .full_width()
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))), .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
) )

View file

@ -535,6 +535,7 @@ impl Render for DapLogToolbarItemView {
)) ))
}) })
.unwrap_or_else(|| "No adapter selected".into()), .unwrap_or_else(|| "No adapter selected".into()),
cx,
)) ))
.menu(move |mut window, cx| { .menu(move |mut window, cx| {
let log_view = log_view.clone(); let log_view = log_view.clone();
@ -632,7 +633,7 @@ impl Render for DapLogToolbarItemView {
.child( .child(
div() div()
.child( .child(
Button::new("clear_log_button", "Clear").on_click(cx.listener( Button::new("clear_log_button", "Clear", cx).on_click(cx.listener(
|this, _, window, cx| { |this, _, window, cx| {
if let Some(log_view) = this.log_view.as_ref() { if let Some(log_view) = this.log_view.as_ref() {
log_view.update(cx, |log_view, cx| { log_view.update(cx, |log_view, cx| {

View file

@ -1715,7 +1715,7 @@ impl Render for DebugPanel {
.justify_center() .justify_center()
.gap_2() .gap_2()
.child( .child(
Button::new("spawn-new-session-empty-state", "New Session") Button::new("spawn-new-session-empty-state", "New Session", cx)
.icon(IconName::Plus) .icon(IconName::Plus)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
.icon_color(Color::Muted) .icon_color(Color::Muted)
@ -1725,7 +1725,7 @@ impl Render for DebugPanel {
}), }),
) )
.child( .child(
Button::new("edit-debug-settings", "Edit debug.json") Button::new("edit-debug-settings", "Edit debug.json", cx)
.icon(IconName::Code) .icon(IconName::Code)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
.color(Color::Muted) .color(Color::Muted)
@ -1739,7 +1739,7 @@ impl Render for DebugPanel {
}), }),
) )
.child( .child(
Button::new("open-debugger-docs", "Debugger Docs") Button::new("open-debugger-docs", "Debugger Docs", cx)
.icon(IconName::Book) .icon(IconName::Book)
.color(Color::Muted) .color(Color::Muted)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
@ -1751,6 +1751,7 @@ impl Render for DebugPanel {
Button::new( Button::new(
"spawn-new-session-install-extensions", "spawn-new-session-install-extensions",
"Debugger Extensions", "Debugger Extensions",
cx,
) )
.icon(IconName::Blocks) .icon(IconName::Blocks)
.color(Color::Muted) .color(Color::Muted)

View file

@ -703,7 +703,7 @@ impl Render for NewProcessModal {
container container
.child( .child(
h_flex().child( h_flex().child(
Button::new("edit-custom-debug", "Edit in debug.json") Button::new("edit-custom-debug", "Edit in debug.json", cx)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.save_debug_scenario(window, cx); this.save_debug_scenario(window, cx);
})) }))
@ -719,7 +719,7 @@ impl Render for NewProcessModal {
), ),
) )
.child( .child(
Button::new("debugger-spawn", "Start") Button::new("debugger-spawn", "Start", cx)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
this.start_new_session(window, cx) this.start_new_session(window, cx)
})) }))
@ -751,7 +751,7 @@ impl Render for NewProcessModal {
.child(div().children( .child(div().children(
KeyBinding::for_action(&*secondary_action, window, cx).map( KeyBinding::for_action(&*secondary_action, window, cx).map(
|keybind| { |keybind| {
Button::new("edit-attach-task", "Edit in debug.json") Button::new("edit-attach-task", "Edit in debug.json", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.key_binding(keybind) .key_binding(keybind)
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {
@ -1389,7 +1389,7 @@ impl PickerDelegate for DebugDelegate {
.children({ .children({
let action = menu::SecondaryConfirm.boxed_clone(); let action = menu::SecondaryConfirm.boxed_clone();
KeyBinding::for_action(&*action, window, cx).map(|keybind| { KeyBinding::for_action(&*action, window, cx).map(|keybind| {
Button::new("edit-debug-task", "Edit in debug.json") Button::new("edit-debug-task", "Edit in debug.json", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.key_binding(keybind) .key_binding(keybind)
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {
@ -1401,7 +1401,7 @@ impl PickerDelegate for DebugDelegate {
if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() { if (current_modifiers.alt || self.matches.is_empty()) && !self.prompt.is_empty() {
let action = picker::ConfirmInput { secondary: false }.boxed_clone(); let action = picker::ConfirmInput { secondary: false }.boxed_clone();
this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| { this.children(KeyBinding::for_action(&*action, window, cx).map(|keybind| {
Button::new("launch-custom", "Launch Custom") Button::new("launch-custom", "Launch Custom", cx)
.key_binding(keybind) .key_binding(keybind)
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx) window.dispatch_action(action.boxed_clone(), cx)
@ -1415,7 +1415,7 @@ impl PickerDelegate for DebugDelegate {
let run_entry_label = let run_entry_label =
if is_recent_selected { "Rerun" } else { "Spawn" }; if is_recent_selected { "Rerun" } else { "Spawn" };
Button::new("spawn", run_entry_label) Button::new("spawn", run_entry_label, cx)
.key_binding(keybind) .key_binding(keybind)
.on_click(|_, window, cx| { .on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx); window.dispatch_action(menu::Confirm.boxed_clone(), cx);

View file

@ -139,13 +139,13 @@ impl Render for DebuggerOnboardingModal {
)), )),
)); ));
let open_panel_button = Button::new("open-panel", "Get Started with the Debugger") let open_panel_button = Button::new("open-panel", "Get Started with the Debugger", cx)
.icon_size(IconSize::Indicator) .icon_size(IconSize::Indicator)
.style(ButtonStyle::Tinted(TintColor::Accent)) .style(ButtonStyle::Tinted(TintColor::Accent))
.full_width() .full_width()
.on_click(cx.listener(Self::open_panel)); .on_click(cx.listener(Self::open_panel));
let blog_post_button = Button::new("view-blog", "Check out the Blog Post") let blog_post_button = Button::new("view-blog", "Check out the Blog Post", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Indicator) .icon_size(IconSize::Indicator)
.icon_color(Color::Muted) .icon_color(Color::Muted)

View file

@ -128,12 +128,12 @@ impl Render for ProjectDiagnosticsEditor {
self.summary.warning_count, plural_suffix self.summary.warning_count, plural_suffix
); );
this.child( this.child(
Button::new("diagnostics-show-warning-label", label).on_click(cx.listener( Button::new("diagnostics-show-warning-label", label, cx).on_click(
|this, _, window, cx| { cx.listener(|this, _, window, cx| {
this.toggle_warnings(&Default::default(), window, cx); this.toggle_warnings(&Default::default(), window, cx);
cx.notify(); cx.notify();
}, }),
)), ),
) )
}) })
} else { } else {

View file

@ -74,7 +74,7 @@ impl Render for DiagnosticIndicator {
let status = if let Some(diagnostic) = &self.current_diagnostic { let status = if let Some(diagnostic) = &self.current_diagnostic {
let message = diagnostic.message.split('\n').next().unwrap().to_string(); let message = diagnostic.message.split('\n').next().unwrap().to_string();
Some( Some(
Button::new("diagnostic_message", message) Button::new("diagnostic_message", message, cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.tooltip(|window, cx| { .tooltip(|window, cx| {
Tooltip::for_action( Tooltip::for_action(

View file

@ -23754,10 +23754,10 @@ impl Render for MissingEditPredictionKeybindingTooltip {
.gap_1() .gap_1()
.items_end() .items_end()
.w_full() .w_full()
.child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| { .child(Button::new("see-key-binding", "See Keybinding", cx).size(ButtonSize::Compact).on_click(|_ev, window, cx| {
window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx) window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
})) }))
.child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| { .child(Button::new("learn-more", "Learn More", cx).size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding"); cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
})), })),
) )
@ -23804,7 +23804,7 @@ fn render_diff_hunk_controls(
.block_mouse_except_scroll() .block_mouse_except_scroll()
.shadow_md() .shadow_md()
.child(if status.has_secondary_hunk() { .child(if status.has_secondary_hunk() {
Button::new(("stage", row as u64), "Stage") Button::new(("stage", row as u64), "Stage", cx)
.alpha(if status.is_pending() { 0.66 } else { 1.0 }) .alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({ .tooltip({
let focus_handle = editor.focus_handle(cx); let focus_handle = editor.focus_handle(cx);
@ -23831,7 +23831,7 @@ fn render_diff_hunk_controls(
} }
}) })
} else { } else {
Button::new(("unstage", row as u64), "Unstage") Button::new(("unstage", row as u64), "Unstage", cx)
.alpha(if status.is_pending() { 0.66 } else { 1.0 }) .alpha(if status.is_pending() { 0.66 } else { 1.0 })
.tooltip({ .tooltip({
let focus_handle = editor.focus_handle(cx); let focus_handle = editor.focus_handle(cx);
@ -23859,7 +23859,7 @@ fn render_diff_hunk_controls(
}) })
}) })
.child( .child(
Button::new(("restore", row as u64), "Restore") Button::new(("restore", row as u64), "Restore", cx)
.tooltip({ .tooltip({
let focus_handle = editor.focus_handle(cx); let focus_handle = editor.focus_handle(cx);
move |window, cx| { move |window, cx| {

View file

@ -56,7 +56,7 @@ impl RenderOnce for FeatureUpsell {
self.docs_url, self.docs_url,
|el, docs_url| { |el, docs_url| {
el.child( el.child(
Button::new("open_docs", "View Documentation") Button::new("open_docs", "View Documentation", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_position(IconPosition::End) .icon_position(IconPosition::End)

View file

@ -596,6 +596,7 @@ impl ExtensionsPage {
Button::new( Button::new(
SharedString::from(format!("rebuild-{}", extension.id)), SharedString::from(format!("rebuild-{}", extension.id)),
"Rebuild", "Rebuild",
cx,
) )
.color(Color::Accent) .color(Color::Accent)
.disabled(matches!(status, ExtensionStatus::Upgrading)) .disabled(matches!(status, ExtensionStatus::Upgrading))
@ -609,7 +610,7 @@ impl ExtensionsPage {
}), }),
) )
.child( .child(
Button::new(SharedString::from(extension.id.clone()), "Uninstall") Button::new(SharedString::from(extension.id.clone()), "Uninstall", cx)
.color(Color::Accent) .color(Color::Accent)
.disabled(matches!(status, ExtensionStatus::Removing)) .disabled(matches!(status, ExtensionStatus::Removing))
.on_click({ .on_click({
@ -626,6 +627,7 @@ impl ExtensionsPage {
Button::new( Button::new(
SharedString::from(format!("configure-{}", extension.id)), SharedString::from(format!("configure-{}", extension.id)),
"Configure", "Configure",
cx,
) )
.color(Color::Accent) .color(Color::Accent)
.disabled(matches!(status, ExtensionStatus::Installing)) .disabled(matches!(status, ExtensionStatus::Installing))
@ -947,6 +949,7 @@ impl ExtensionsPage {
install_or_uninstall: Button::new( install_or_uninstall: Button::new(
SharedString::from(extension.id.clone()), SharedString::from(extension.id.clone()),
"Install", "Install",
cx,
), ),
configure: None, configure: None,
upgrade: None, upgrade: None,
@ -963,6 +966,7 @@ impl ExtensionsPage {
install_or_uninstall: Button::new( install_or_uninstall: Button::new(
SharedString::from(extension.id.clone()), SharedString::from(extension.id.clone()),
"Install", "Install",
cx,
) )
.on_click({ .on_click({
let extension_id = extension.id.clone(); let extension_id = extension.id.clone();
@ -980,6 +984,7 @@ impl ExtensionsPage {
install_or_uninstall: Button::new( install_or_uninstall: Button::new(
SharedString::from(extension.id.clone()), SharedString::from(extension.id.clone()),
"Install", "Install",
cx,
) )
.disabled(true), .disabled(true),
configure: None, configure: None,
@ -989,23 +994,27 @@ impl ExtensionsPage {
install_or_uninstall: Button::new( install_or_uninstall: Button::new(
SharedString::from(extension.id.clone()), SharedString::from(extension.id.clone()),
"Uninstall", "Uninstall",
cx,
) )
.disabled(true), .disabled(true),
configure: is_configurable.then(|| { configure: is_configurable.then(|| {
Button::new( Button::new(
SharedString::from(format!("configure-{}", extension.id)), SharedString::from(format!("configure-{}", extension.id)),
"Configure", "Configure",
cx,
) )
.disabled(true) .disabled(true)
}), }),
upgrade: Some( upgrade: Some(
Button::new(SharedString::from(extension.id.clone()), "Upgrade").disabled(true), Button::new(SharedString::from(extension.id.clone()), "Upgrade", cx)
.disabled(true),
), ),
}, },
ExtensionStatus::Installed(installed_version) => ExtensionCardButtons { ExtensionStatus::Installed(installed_version) => ExtensionCardButtons {
install_or_uninstall: Button::new( install_or_uninstall: Button::new(
SharedString::from(extension.id.clone()), SharedString::from(extension.id.clone()),
"Uninstall", "Uninstall",
cx,
) )
.on_click({ .on_click({
let extension_id = extension.id.clone(); let extension_id = extension.id.clone();
@ -1022,6 +1031,7 @@ impl ExtensionsPage {
Button::new( Button::new(
SharedString::from(format!("configure-{}", extension.id)), SharedString::from(format!("configure-{}", extension.id)),
"Configure", "Configure",
cx,
) )
.on_click({ .on_click({
let extension_id = extension.id.clone(); let extension_id = extension.id.clone();
@ -1047,7 +1057,7 @@ impl ExtensionsPage {
None None
} else { } else {
Some( Some(
Button::new(SharedString::from(extension.id.clone()), "Upgrade") Button::new(SharedString::from(extension.id.clone()), "Upgrade", cx)
.when(!is_compatible, |upgrade_button| { .when(!is_compatible, |upgrade_button| {
upgrade_button.disabled(true).tooltip({ upgrade_button.disabled(true).tooltip({
let version = extension.manifest.version.clone(); let version = extension.manifest.version.clone();
@ -1085,12 +1095,14 @@ impl ExtensionsPage {
install_or_uninstall: Button::new( install_or_uninstall: Button::new(
SharedString::from(extension.id.clone()), SharedString::from(extension.id.clone()),
"Uninstall", "Uninstall",
cx,
) )
.disabled(true), .disabled(true),
configure: is_configurable.then(|| { configure: is_configurable.then(|| {
Button::new( Button::new(
SharedString::from(format!("configure-{}", extension.id)), SharedString::from(format!("configure-{}", extension.id)),
"Configure", "Configure",
cx,
) )
.disabled(true) .disabled(true)
}), }),
@ -1394,7 +1406,7 @@ impl Render for ExtensionsPage {
.justify_between() .justify_between()
.child(Headline::new("Extensions").size(HeadlineSize::XLarge)) .child(Headline::new("Extensions").size(HeadlineSize::XLarge))
.child( .child(
Button::new("install-dev-extension", "Install Dev Extension") Button::new("install-dev-extension", "Install Dev Extension", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.size(ButtonSize::Large) .size(ButtonSize::Large)
.on_click(|_event, window, cx| { .on_click(|_event, window, cx| {
@ -1470,7 +1482,7 @@ impl Render for ExtensionsPage {
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.overflow_x_scroll() .overflow_x_scroll()
.child( .child(
Button::new("filter-all-categories", "All") Button::new("filter-all-categories", "All", cx)
.when(self.provides_filter.is_none(), |button| { .when(self.provides_filter.is_none(), |button| {
button.style(ButtonStyle::Filled) button.style(ButtonStyle::Filled)
}) })
@ -1493,7 +1505,7 @@ impl Render for ExtensionsPage {
let button_id = SharedString::from(format!("filter-category-{}", label)); let button_id = SharedString::from(format!("filter-category-{}", label));
Some( Some(
Button::new(button_id, label) Button::new(button_id, label, cx)
.style(if self.provides_filter == Some(provides) { .style(if self.provides_filter == Some(provides) {
ButtonStyle::Filled ButtonStyle::Filled
} else { } else {

View file

@ -68,7 +68,7 @@ impl Render for FeedbackModal {
) )
.child(Label::new("Thanks for using Zed! To share your experience with us, reach for the channel that's the most appropriate:")) .child(Label::new("Thanks for using Zed! To share your experience with us, reach for the channel that's the most appropriate:"))
.child( .child(
Button::new("file-a-bug-report", "File a Bug Report") Button::new("file-a-bug-report", "File a Bug Report", cx)
.full_width() .full_width()
.icon(IconName::Debug) .icon(IconName::Debug)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
@ -79,7 +79,7 @@ impl Render for FeedbackModal {
})), })),
) )
.child( .child(
Button::new("request-a-feature", "Request a Feature") Button::new("request-a-feature", "Request a Feature", cx)
.full_width() .full_width()
.icon(IconName::Sparkle) .icon(IconName::Sparkle)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
@ -90,7 +90,7 @@ impl Render for FeedbackModal {
})), })),
) )
.child( .child(
Button::new("send-us_an-email", "Send an Email") Button::new("send-us_an-email", "Send an Email", cx)
.full_width() .full_width()
.icon(IconName::Envelope) .icon(IconName::Envelope)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
@ -101,7 +101,7 @@ impl Render for FeedbackModal {
})), })),
) )
.child( .child(
Button::new("zed_repository", "GitHub Repository") Button::new("zed_repository", "GitHub Repository", cx)
.full_width() .full_width()
.icon(IconName::Github) .icon(IconName::Github)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)

View file

@ -1779,7 +1779,7 @@ impl PickerDelegate for FileFinderDelegate {
}), }),
) )
.child( .child(
Button::new("open-selection", "Open") Button::new("open-selection", "Open", cx)
.key_binding( .key_binding(
KeyBinding::for_action_in( KeyBinding::for_action_in(
&menu::Confirm, &menu::Confirm,

View file

@ -301,6 +301,7 @@ impl BlameRenderer for GitBlameRenderer {
Button::new( Button::new(
"pull-request-button", "pull-request-button",
format!("#{}", pr.number), format!("#{}", pr.number),
cx,
) )
.color(Color::Muted) .color(Color::Muted)
.icon(IconName::PullRequest) .icon(IconName::PullRequest)
@ -318,6 +319,7 @@ impl BlameRenderer for GitBlameRenderer {
Button::new( Button::new(
"commit-sha-button", "commit-sha-button",
short_commit_id.clone(), short_commit_id.clone(),
cx,
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.color(Color::Muted) .color(Color::Muted)

View file

@ -363,7 +363,7 @@ impl CommitModal {
.map(|b| b.name().to_owned()) .map(|b| b.name().to_owned())
.unwrap_or_else(|| "<no branch>".to_owned()); .unwrap_or_else(|| "<no branch>".to_owned());
let branch_picker_button = panel_button(branch) let branch_picker_button = panel_button(branch, cx)
.icon(IconName::GitBranch) .icon(IconName::GitBranch)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Placeholder) .icon_color(Color::Placeholder)

View file

@ -283,6 +283,7 @@ impl Render for CommitTooltip {
Button::new( Button::new(
"pull-request-button", "pull-request-button",
format!("#{}", pr.number), format!("#{}", pr.number),
cx,
) )
.color(Color::Muted) .color(Color::Muted)
.icon(IconName::PullRequest) .icon(IconName::PullRequest)
@ -300,6 +301,7 @@ impl Render for CommitTooltip {
Button::new( Button::new(
"commit-sha-button", "commit-sha-button",
short_commit_id.clone(), short_commit_id.clone(),
cx,
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.color(Color::Muted) .color(Color::Muted)

View file

@ -394,7 +394,7 @@ fn render_conflict_buttons(
.gap_1() .gap_1()
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.child( .child(
Button::new("head", "Use HEAD") Button::new("head", "Use HEAD", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click({ .on_click({
let editor = editor.clone(); let editor = editor.clone();
@ -414,7 +414,7 @@ fn render_conflict_buttons(
}), }),
) )
.child( .child(
Button::new("origin", "Use Origin") Button::new("origin", "Use Origin", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click({ .on_click({
let editor = editor.clone(); let editor = editor.clone();
@ -434,7 +434,7 @@ fn render_conflict_buttons(
}), }),
) )
.child( .child(
Button::new("both", "Use Both") Button::new("both", "Use Both", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click({ .on_click({
let editor = editor.clone(); let editor = editor.clone();

View file

@ -3327,7 +3327,7 @@ impl GitPanel {
.px_2() .px_2()
.justify_between() .justify_between()
.child( .child(
panel_button(change_string) panel_button(change_string, cx)
.color(Color::Muted) .color(Color::Muted)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Open Diff", "Open Diff",
@ -3345,7 +3345,7 @@ impl GitPanel {
.gap_1() .gap_1()
.child(self.render_overflow_menu("overflow_menu")) .child(self.render_overflow_menu("overflow_menu"))
.child( .child(
panel_filled_button(text) panel_filled_button(text, cx)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
tooltip, tooltip,
action.as_ref(), action.as_ref(),
@ -3604,7 +3604,7 @@ impl GitPanel {
), ),
) )
.child( .child(
panel_button("Cancel") panel_button("Cancel", cx)
.size(ButtonSize::Default) .size(ButtonSize::Default)
.on_click(cx.listener(|this, _, _, cx| this.set_amend_pending(false, cx))), .on_click(cx.listener(|this, _, _, cx| this.set_amend_pending(false, cx))),
) )
@ -3703,7 +3703,7 @@ impl GitPanel {
let worktree_count = self.project.read(cx).visible_worktrees(cx).count(); let worktree_count = self.project.read(cx).visible_worktrees(cx).count();
(worktree_count > 0 && self.active_repository.is_none()).then(|| { (worktree_count > 0 && self.active_repository.is_none()).then(|| {
h_flex().w_full().justify_around().child( h_flex().w_full().justify_around().child(
panel_filled_button("Initialize Repository") panel_filled_button("Initialize Repository", cx)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"git init", "git init",
&git::Init, &git::Init,
@ -4841,7 +4841,7 @@ impl RenderOnce for PanelRepoFooter {
util::truncate_and_trailoff(branch_name.trim_ascii(), branch_display_len) util::truncate_and_trailoff(branch_name.trim_ascii(), branch_display_len)
}; };
let repo_selector_trigger = Button::new("repo-selector", truncated_repo_name) let repo_selector_trigger = Button::new("repo-selector", truncated_repo_name, cx)
.style(ButtonStyle::Transparent) .style(ButtonStyle::Transparent)
.size(ButtonSize::None) .size(ButtonSize::None)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
@ -4862,7 +4862,7 @@ impl RenderOnce for PanelRepoFooter {
.anchor(Corner::BottomLeft) .anchor(Corner::BottomLeft)
.into_any_element(); .into_any_element();
let branch_selector_button = Button::new("branch-selector", truncated_branch_name) let branch_selector_button = Button::new("branch-selector", truncated_branch_name, cx)
.style(ButtonStyle::Transparent) .style(ButtonStyle::Transparent)
.size(ButtonSize::None) .size(ButtonSize::None)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)

View file

@ -118,13 +118,13 @@ impl Render for GitOnboardingModal {
)), )),
)); ));
let open_panel_button = Button::new("open-panel", "Get Started with the Git Panel") let open_panel_button = Button::new("open-panel", "Get Started with the Git Panel", cx)
.icon_size(IconSize::Indicator) .icon_size(IconSize::Indicator)
.style(ButtonStyle::Tinted(TintColor::Accent)) .style(ButtonStyle::Tinted(TintColor::Accent))
.full_width() .full_width()
.on_click(cx.listener(Self::open_panel)); .on_click(cx.listener(Self::open_panel));
let blog_post_button = Button::new("view-blog", "Check out the Blog Post") let blog_post_button = Button::new("view-blog", "Check out the Blog Post", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Indicator) .icon_size(IconSize::Indicator)
.icon_color(Color::Muted) .icon_color(Color::Muted)

View file

@ -759,7 +759,7 @@ impl Render for ProjectDiff {
}) })
.child( .child(
h_flex().justify_around().mt_1().child( h_flex().justify_around().mt_1().child(
Button::new("project-diff-close-button", "Close") Button::new("project-diff-close-button", "Close", cx)
// .style(ButtonStyle::Transparent) // .style(ButtonStyle::Transparent)
.key_binding(KeyBinding::for_action_in( .key_binding(KeyBinding::for_action_in(
&CloseActiveItem::default(), &CloseActiveItem::default(),
@ -936,7 +936,7 @@ impl Render for ProjectDiffToolbar {
h_group_sm() h_group_sm()
.when(button_states.selection, |el| { .when(button_states.selection, |el| {
el.child( el.child(
Button::new("stage", "Toggle Staged") Button::new("stage", "Toggle Staged", cx)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Toggle Staged", "Toggle Staged",
&ToggleStaged, &ToggleStaged,
@ -950,7 +950,7 @@ impl Render for ProjectDiffToolbar {
}) })
.when(!button_states.selection, |el| { .when(!button_states.selection, |el| {
el.child( el.child(
Button::new("stage", "Stage") Button::new("stage", "Stage", cx)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Stage and go to next hunk", "Stage and go to next hunk",
&StageAndNext, &StageAndNext,
@ -961,7 +961,7 @@ impl Render for ProjectDiffToolbar {
})), })),
) )
.child( .child(
Button::new("unstage", "Unstage") Button::new("unstage", "Unstage", cx)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Unstage and go to next hunk", "Unstage and go to next hunk",
&UnstageAndNext, &UnstageAndNext,
@ -1011,7 +1011,7 @@ impl Render for ProjectDiffToolbar {
button_states.unstage_all && !button_states.stage_all, button_states.unstage_all && !button_states.stage_all,
|el| { |el| {
el.child( el.child(
Button::new("unstage-all", "Unstage All") Button::new("unstage-all", "Unstage All", cx)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Unstage all changes", "Unstage all changes",
&UnstageAll, &UnstageAll,
@ -1030,7 +1030,7 @@ impl Render for ProjectDiffToolbar {
// todo make it so that changing to say "Unstaged" // todo make it so that changing to say "Unstaged"
// doesn't change the position. // doesn't change the position.
div().child( div().child(
Button::new("stage-all", "Stage All") Button::new("stage-all", "Stage All", cx)
.disabled(!button_states.stage_all) .disabled(!button_states.stage_all)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Stage all changes", "Stage all changes",
@ -1045,7 +1045,7 @@ impl Render for ProjectDiffToolbar {
}, },
) )
.child( .child(
Button::new("commit", "Commit") Button::new("commit", "Commit", cx)
.tooltip(Tooltip::for_action_title_in( .tooltip(Tooltip::for_action_title_in(
"Commit", "Commit",
&Commit, &Commit,

View file

@ -219,7 +219,7 @@ impl Render for CursorPosition {
let context = self.context.clone(); let context = self.context.clone();
el.child( el.child(
Button::new("go-to-line-column", text) Button::new("go-to-line-column", text, cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
if let Some(workspace) = this.workspace.upgrade() { if let Some(workspace) = this.workspace.upgrade() {

View file

@ -78,7 +78,7 @@ impl Render for ImageInfo {
); );
div().child( div().child(
Button::new("image-metadata", components.join("")).label_size(LabelSize::Small), Button::new("image-metadata", components.join(""), cx).label_size(LabelSize::Small),
) )
} }
} }

View file

@ -1017,13 +1017,14 @@ impl Render for ConfigurationView {
List::new() List::new()
.child( .child(
InstructionListItem::new( InstructionListItem::new(
cx,
"Create one by visiting", "Create one by visiting",
Some("Anthropic's settings"), Some("Anthropic's settings"),
Some("https://console.anthropic.com/settings/keys") Some("https://console.anthropic.com/settings/keys")
) )
) )
.child( .child(
InstructionListItem::text_only("Paste your API key below and hit enter to start using the assistant") InstructionListItem::text_only(cx, "Paste your API key below and hit enter to start using the assistant")
) )
) )
.child( .child(
@ -1066,7 +1067,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-key", "Reset Key") Button::new("reset-key", "Reset Key", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(Some(IconName::Trash)) .icon(Some(IconName::Trash))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -1232,7 +1232,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-key", "Reset Key") Button::new("reset-key", "Reset Key", cx)
.icon(Some(IconName::Trash)) .icon(Some(IconName::Trash))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
@ -1257,6 +1257,7 @@ impl Render for ConfigurationView {
List::new() List::new()
.child( .child(
InstructionListItem::new( InstructionListItem::new(
cx,
"Grant permissions to the strategy you'll use according to the:", "Grant permissions to the strategy you'll use according to the:",
Some("Prerequisites"), Some("Prerequisites"),
Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"), Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"),
@ -1264,6 +1265,7 @@ impl Render for ConfigurationView {
) )
.child( .child(
InstructionListItem::new( InstructionListItem::new(
cx,
"Select the models you would like access to:", "Select the models you would like access to:",
Some("Bedrock Model Catalog"), Some("Bedrock Model Catalog"),
Some("https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess"), Some("https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess"),
@ -1365,19 +1367,23 @@ impl ConfigurationView {
.child( .child(
List::new() List::new()
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Create an IAM user in the AWS console with programmatic access", "Create an IAM user in the AWS console with programmatic access",
Some("IAM Console"), Some("IAM Console"),
Some("https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users"), Some("https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users"),
)) ))
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Attach the necessary Bedrock permissions to this ", "Attach the necessary Bedrock permissions to this ",
Some("user"), Some("user"),
Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"), Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"),
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Copy the access key ID and secret access key when provided", "Copy the access key ID and secret access key when provided",
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Enter these credentials below", "Enter these credentials below",
)), )),
) )

View file

@ -410,12 +410,17 @@ impl LanguageModelProvider for CloudLanguageModelProvider {
return None; return None;
} }
Some( Some(
render_accept_terms(view, state.accept_terms_of_service_task.is_some(), { render_accept_terms(
let state = self.state.clone(); view,
move |_window, cx| { state.accept_terms_of_service_task.is_some(),
state.update(cx, |state, cx| state.accept_terms_of_service(cx)); {
} let state = self.state.clone();
}) move |_window, cx| {
state.update(cx, |state, cx| state.accept_terms_of_service(cx));
}
},
cx,
)
.into_any_element(), .into_any_element(),
) )
} }
@ -429,11 +434,12 @@ fn render_accept_terms(
view_kind: LanguageModelProviderTosView, view_kind: LanguageModelProviderTosView,
accept_terms_of_service_in_progress: bool, accept_terms_of_service_in_progress: bool,
accept_terms_callback: impl Fn(&mut Window, &mut App) + 'static, accept_terms_callback: impl Fn(&mut Window, &mut App) + 'static,
app: &App,
) -> impl IntoElement { ) -> impl IntoElement {
let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart); let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart);
let thread_empty_state = matches!(view_kind, LanguageModelProviderTosView::ThreadEmptyState); let thread_empty_state = matches!(view_kind, LanguageModelProviderTosView::ThreadEmptyState);
let terms_button = Button::new("terms_of_service", "Terms of Service") let terms_button = Button::new("terms_of_service", "Terms of Service", app)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_color(Color::Muted) .icon_color(Color::Muted)
@ -442,7 +448,7 @@ fn render_accept_terms(
.on_click(move |_, _window, cx| cx.open_url("https://zed.dev/terms-of-service")); .on_click(move |_, _window, cx| cx.open_url("https://zed.dev/terms-of-service"));
let button_container = h_flex().child( let button_container = h_flex().child(
Button::new("accept_terms", "I accept the Terms of Service") Button::new("accept_terms", "I accept the Terms of Service", app)
.when(!thread_empty_state, |this| { .when(!thread_empty_state, |this| {
this.full_width() this.full_width()
.style(ButtonStyle::Tinted(TintColor::Accent)) .style(ButtonStyle::Tinted(TintColor::Accent))
@ -1135,19 +1141,19 @@ impl RenderOnce for ZedAiConfiguration {
}; };
let manage_subscription_buttons = if is_pro { let manage_subscription_buttons = if is_pro {
Button::new("manage_settings", "Manage Subscription") Button::new("manage_settings", "Manage Subscription", _cx)
.full_width() .full_width()
.style(ButtonStyle::Tinted(TintColor::Accent)) .style(ButtonStyle::Tinted(TintColor::Accent))
.on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))) .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx)))
.into_any_element() .into_any_element()
} else if self.plan.is_none() || self.eligible_for_trial { } else if self.plan.is_none() || self.eligible_for_trial {
Button::new("start_trial", "Start 14-day Free Pro Trial") Button::new("start_trial", "Start 14-day Free Pro Trial", _cx)
.full_width() .full_width()
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(|_, _, cx| cx.open_url(&zed_urls::start_trial_url(cx))) .on_click(|_, _, cx| cx.open_url(&zed_urls::start_trial_url(cx)))
.into_any_element() .into_any_element()
} else { } else {
Button::new("upgrade", "Upgrade to Pro") Button::new("upgrade", "Upgrade to Pro", _cx)
.full_width() .full_width()
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))) .on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx)))
@ -1159,7 +1165,7 @@ impl RenderOnce for ZedAiConfiguration {
.gap_2() .gap_2()
.child(Label::new("Sign in to have access to Zed's complete agentic experience with hosted models.")) .child(Label::new("Sign in to have access to Zed's complete agentic experience with hosted models."))
.child( .child(
Button::new("sign_in", "Sign In to use Zed AI") Button::new("sign_in", "Sign In to use Zed AI", _cx)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.icon(IconName::Github) .icon(IconName::Github)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -1183,12 +1189,13 @@ impl RenderOnce for ZedAiConfiguration {
let callback = self.accept_terms_of_service_callback.clone(); let callback = self.accept_terms_of_service_callback.clone();
move |window, cx| (callback)(window, cx) move |window, cx| (callback)(window, cx)
}, },
_cx,
)) ))
}) })
.map(|this| { .map(|this| {
if self.has_accepted_terms_of_service && self.account_too_young { if self.has_accepted_terms_of_service && self.account_too_young {
this.child(young_account_banner).child( this.child(young_account_banner).child(
Button::new("upgrade", "Upgrade to Pro") Button::new("upgrade", "Upgrade to Pro", _cx)
.style(ui::ButtonStyle::Tinted(ui::TintColor::Accent)) .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent))
.full_width() .full_width()
.on_click(|_, _, cx| { .on_click(|_, _, cx| {

View file

@ -670,7 +670,7 @@ impl Render for ConfigurationView {
.child(Label::new("Authorized")), .child(Label::new("Authorized")),
) )
.child( .child(
Button::new("sign_out", "Sign Out") Button::new("sign_out", "Sign Out", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(|_, window, cx| { .on_click(|_, window, cx| {
window.dispatch_action(copilot::SignOut.boxed_clone(), cx); window.dispatch_action(copilot::SignOut.boxed_clone(), cx);
@ -709,7 +709,7 @@ impl Render for ConfigurationView {
const LABEL: &str = "To use Zed's agent with GitHub Copilot, you need to be logged in to GitHub. Note that your GitHub account must have an active Copilot Chat subscription."; const LABEL: &str = "To use Zed's agent with GitHub Copilot, you need to be logged in to GitHub. Note that your GitHub account must have an active Copilot Chat subscription.";
v_flex().gap_2().child(Label::new(LABEL)).child( v_flex().gap_2().child(Label::new(LABEL)).child(
Button::new("sign_in", "Sign in to use GitHub Copilot") Button::new("sign_in", "Sign in to use GitHub Copilot", cx)
.icon_color(Color::Muted) .icon_color(Color::Muted)
.icon(IconName::Github) .icon(IconName::Github)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)

View file

@ -679,11 +679,13 @@ impl Render for ConfigurationView {
.child( .child(
List::new() List::new()
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Get your API key from the", "Get your API key from the",
Some("DeepSeek console"), Some("DeepSeek console"),
Some("https://platform.deepseek.com/api_keys"), Some("https://platform.deepseek.com/api_keys"),
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant", "Paste your API key below and hit enter to start using the assistant",
)), )),
) )
@ -728,7 +730,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-key", "Reset Key") Button::new("reset-key", "Reset Key", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(Some(IconName::Trash)) .icon(Some(IconName::Trash))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -884,11 +884,13 @@ impl Render for ConfigurationView {
.child( .child(
List::new() List::new()
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Create one by visiting", "Create one by visiting",
Some("Google AI's console"), Some("Google AI's console"),
Some("https://aistudio.google.com/app/apikey"), Some("https://aistudio.google.com/app/apikey"),
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant", "Paste your API key below and hit enter to start using the assistant",
)), )),
) )
@ -931,7 +933,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-key", "Reset Key") Button::new("reset-key", "Reset Key", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(Some(IconName::Trash)) .icon(Some(IconName::Trash))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -668,9 +668,11 @@ impl Render for ConfigurationView {
v_flex().gap_1().child(Label::new(lmstudio_intro)).child( v_flex().gap_1().child(Label::new(lmstudio_intro)).child(
List::new() List::new()
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"LM Studio needs to be running with at least one model downloaded.", "LM Studio needs to be running with at least one model downloaded.",
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"To get your first model, try running `lms get qwen2.5-coder-7b`", "To get your first model, try running `lms get qwen2.5-coder-7b`",
)), )),
), ),
@ -687,7 +689,7 @@ impl Render for ConfigurationView {
.map(|this| { .map(|this| {
if is_authenticated { if is_authenticated {
this.child( this.child(
Button::new("lmstudio-site", "LM Studio") Button::new("lmstudio-site", "LM Studio", cx)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -702,6 +704,7 @@ impl Render for ConfigurationView {
Button::new( Button::new(
"download_lmstudio_button", "download_lmstudio_button",
"Download LM Studio", "Download LM Studio",
cx,
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
@ -715,7 +718,7 @@ impl Render for ConfigurationView {
} }
}) })
.child( .child(
Button::new("view-models", "Model Catalog") Button::new("view-models", "Model Catalog", cx)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -741,7 +744,7 @@ impl Render for ConfigurationView {
) )
} else { } else {
this.child( this.child(
Button::new("retry_lmstudio_models", "Connect") Button::new("retry_lmstudio_models", "Connect", cx)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
.icon(IconName::PlayFilled) .icon(IconName::PlayFilled)

View file

@ -848,14 +848,17 @@ impl Render for ConfigurationView {
.child( .child(
List::new() List::new()
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Create one by visiting", "Create one by visiting",
Some("Mistral's console"), Some("Mistral's console"),
Some("https://console.mistral.ai/api-keys"), Some("https://console.mistral.ai/api-keys"),
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Ensure your Mistral account has credits", "Ensure your Mistral account has credits",
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant", "Paste your API key below and hit enter to start using the assistant",
)), )),
) )
@ -898,7 +901,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-key", "Reset Key") Button::new("reset-key", "Reset Key", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(Some(IconName::Trash)) .icon(Some(IconName::Trash))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -587,8 +587,9 @@ impl Render for ConfigurationView {
.child( .child(
v_flex().gap_1().child(Label::new(ollama_intro)).child( v_flex().gap_1().child(Label::new(ollama_intro)).child(
List::new() List::new()
.child(InstructionListItem::text_only("Ollama must be running with at least one model installed to use it in the assistant.")) .child(InstructionListItem::text_only(cx, "Ollama must be running with at least one model installed to use it in the assistant."))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Once installed, try `ollama run llama3.2`", "Once installed, try `ollama run llama3.2`",
)), )),
), ),
@ -605,7 +606,7 @@ impl Render for ConfigurationView {
.map(|this| { .map(|this| {
if is_authenticated { if is_authenticated {
this.child( this.child(
Button::new("ollama-site", "Ollama") Button::new("ollama-site", "Ollama", cx)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -618,6 +619,7 @@ impl Render for ConfigurationView {
Button::new( Button::new(
"download_ollama_button", "download_ollama_button",
"Download Ollama", "Download Ollama",
cx,
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
@ -631,7 +633,7 @@ impl Render for ConfigurationView {
} }
}) })
.child( .child(
Button::new("view-models", "View All Models") Button::new("view-models", "View All Models", cx)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -655,7 +657,7 @@ impl Render for ConfigurationView {
) )
} else { } else {
this.child( this.child(
Button::new("retry_ollama_models", "Connect") Button::new("retry_ollama_models", "Connect", cx)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::XSmall) .icon_size(IconSize::XSmall)
.icon(IconName::PlayFilled) .icon(IconName::PlayFilled)

View file

@ -812,14 +812,17 @@ impl Render for ConfigurationView {
.child( .child(
List::new() List::new()
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Create one by visiting", "Create one by visiting",
Some("OpenAI's console"), Some("OpenAI's console"),
Some("https://platform.openai.com/api-keys"), Some("https://platform.openai.com/api-keys"),
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Ensure your OpenAI account has credits", "Ensure your OpenAI account has credits",
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant", "Paste your API key below and hit enter to start using the assistant",
)), )),
) )
@ -857,7 +860,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-api-key", "Reset API Key") Button::new("reset-api-key", "Reset API Key",cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::Undo) .icon(IconName::Undo)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -891,7 +894,7 @@ impl Render for ConfigurationView {
.child(Label::new("Zed also supports OpenAI-compatible models.")), .child(Label::new("Zed also supports OpenAI-compatible models.")),
) )
.child( .child(
Button::new("docs", "Learn More") Button::new("docs", "Learn More", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted)

View file

@ -505,7 +505,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-api-key", "Reset API Key") Button::new("reset-api-key", "Reset API Key",cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::Undo) .icon(IconName::Undo)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -859,14 +859,17 @@ impl Render for ConfigurationView {
.child( .child(
List::new() List::new()
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Create an API key by visiting", "Create an API key by visiting",
Some("OpenRouter's console"), Some("OpenRouter's console"),
Some("https://openrouter.ai/keys"), Some("https://openrouter.ai/keys"),
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Ensure your OpenRouter account has credits", "Ensure your OpenRouter account has credits",
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the assistant", "Paste your API key below and hit enter to start using the assistant",
)), )),
) )
@ -909,7 +912,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-key", "Reset Key") Button::new("reset-key", "Reset Key",cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(Some(IconName::Trash)) .icon(Some(IconName::Trash))
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -517,11 +517,13 @@ impl Render for ConfigurationView {
.child( .child(
List::new() List::new()
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Create one by visiting", "Create one by visiting",
Some("Vercel v0's console"), Some("Vercel v0's console"),
Some("https://v0.dev/chat/settings/keys"), Some("https://v0.dev/chat/settings/keys"),
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the agent", "Paste your API key below and hit enter to start using the agent",
)), )),
) )
@ -559,7 +561,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-api-key", "Reset API Key") Button::new("reset-api-key", "Reset API Key",cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::Undo) .icon(IconName::Undo)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -507,11 +507,13 @@ impl Render for ConfigurationView {
.child( .child(
List::new() List::new()
.child(InstructionListItem::new( .child(InstructionListItem::new(
cx,
"Create one by visiting", "Create one by visiting",
Some("xAI console"), Some("xAI console"),
Some("https://console.x.ai/team/default/api-keys"), Some("https://console.x.ai/team/default/api-keys"),
)) ))
.child(InstructionListItem::text_only( .child(InstructionListItem::text_only(
cx,
"Paste your API key below and hit enter to start using the agent", "Paste your API key below and hit enter to start using the agent",
)), )),
) )
@ -549,7 +551,7 @@ impl Render for ConfigurationView {
})), })),
) )
.child( .child(
Button::new("reset-api-key", "Reset API Key") Button::new("reset-api-key", "Reset API Key",cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::Undo) .icon(IconName::Undo)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -2,27 +2,31 @@ use gpui::{AnyElement, IntoElement, ParentElement, SharedString};
use ui::{ListItem, prelude::*}; use ui::{ListItem, prelude::*};
/// A reusable list item component for adding LLM provider configuration instructions /// A reusable list item component for adding LLM provider configuration instructions
pub struct InstructionListItem { pub struct InstructionListItem<'app> {
app: &'app App,
label: SharedString, label: SharedString,
button_label: Option<SharedString>, button_label: Option<SharedString>,
button_link: Option<String>, button_link: Option<String>,
} }
impl InstructionListItem { impl<'app> InstructionListItem<'app> {
pub fn new( pub fn new(
app: &'app App,
label: impl Into<SharedString>, label: impl Into<SharedString>,
button_label: Option<impl Into<SharedString>>, button_label: Option<impl Into<SharedString>>,
button_link: Option<impl Into<String>>, button_link: Option<impl Into<String>>,
) -> Self { ) -> Self {
Self { Self {
app,
label: label.into(), label: label.into(),
button_label: button_label.map(|l| l.into()), button_label: button_label.map(|l| l.into()),
button_link: button_link.map(|l| l.into()), button_link: button_link.map(|l| l.into()),
} }
} }
pub fn text_only(label: impl Into<SharedString>) -> Self { pub fn text_only(app: &'app App, label: impl Into<SharedString>) -> Self {
Self { Self {
app,
label: label.into(), label: label.into(),
button_label: None, button_label: None,
button_link: None, button_link: None,
@ -30,7 +34,7 @@ impl InstructionListItem {
} }
} }
impl IntoElement for InstructionListItem { impl IntoElement for InstructionListItem<'_> {
type Element = AnyElement; type Element = AnyElement;
fn into_element(self) -> Self::Element { fn into_element(self) -> Self::Element {
@ -44,7 +48,7 @@ impl IntoElement for InstructionListItem {
.flex_wrap() .flex_wrap()
.child(Label::new(self.label)) .child(Label::new(self.label))
.child( .child(
Button::new(unique_id, button_label) Button::new(unique_id, button_label, self.app)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)

View file

@ -55,7 +55,7 @@ impl Render for ActiveBufferLanguage {
}; };
el.child( el.child(
Button::new("change-language", active_language_text) Button::new("change-language", active_language_text, cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
if let Some(workspace) = this.workspace.upgrade() { if let Some(workspace) = this.workspace.upgrade() {

View file

@ -206,12 +206,12 @@ impl Render for KeyContextView {
.mt_4() .mt_4()
.gap_4() .gap_4()
.child( .child(
Button::new("open_documentation", "Open Documentation") Button::new("open_documentation", "Open Documentation", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.on_click(|_, _, cx| cx.open_url("https://zed.dev/docs/key-bindings")), .on_click(|_, _, cx| cx.open_url("https://zed.dev/docs/key-bindings")),
) )
.child( .child(
Button::new("view_default_keymap", "View default keymap") Button::new("view_default_keymap", "View default keymap", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.key_binding(ui::KeyBinding::for_action( .key_binding(ui::KeyBinding::for_action(
&zed_actions::OpenDefaultKeymap, &zed_actions::OpenDefaultKeymap,
@ -223,7 +223,7 @@ impl Render for KeyContextView {
}), }),
) )
.child( .child(
Button::new("edit_your_keymap", "Edit your keymap") Button::new("edit_your_keymap", "Edit your keymap", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.key_binding(ui::KeyBinding::for_action(&zed_actions::OpenKeymap, window, cx)) .key_binding(ui::KeyBinding::for_action(&zed_actions::OpenKeymap, window, cx))
.on_click(|_, window, cx| { .on_click(|_, window, cx| {

View file

@ -1374,6 +1374,7 @@ impl Render for LspLogToolbarItemView {
)) ))
}) })
.unwrap_or_else(|| "No server selected".into()), .unwrap_or_else(|| "No server selected".into()),
cx,
) )
.icon(IconName::ChevronDown) .icon(IconName::ChevronDown)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -1431,10 +1432,14 @@ impl Render for LspLogToolbarItemView {
PopoverMenu::new("LspViewSelector") PopoverMenu::new("LspViewSelector")
.anchor(Corner::TopLeft) .anchor(Corner::TopLeft)
.trigger( .trigger(
Button::new("language_server_menu_header", server.selected_entry.label()) Button::new(
.icon(IconName::ChevronDown) "language_server_menu_header",
.icon_size(IconSize::Small) server.selected_entry.label(),
.icon_color(Color::Muted), cx,
)
.icon(IconName::ChevronDown)
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
) )
.menu(move |window, cx| { .menu(move |window, cx| {
let log_toolbar_view = log_toolbar_view.clone(); let log_toolbar_view = log_toolbar_view.clone();
@ -1516,22 +1521,26 @@ impl Render for LspLogToolbarItemView {
.gap_0p5() .gap_0p5()
.child(lsp_menu) .child(lsp_menu)
.children(view_selector) .children(view_selector)
.child( .child({
let trace_button =
Button::new("language_server_trace_level_selector", "Trace level", cx)
.icon(IconName::ChevronDown)
.icon_size(IconSize::Small)
.icon_color(Color::Muted);
let log_button =
Button::new("language_server_log_level_selector", "Log level", cx)
.icon(IconName::ChevronDown)
.icon_size(IconSize::Small)
.icon_color(Color::Muted);
log_view.update(cx, |this, _cx| match this.active_entry_kind { log_view.update(cx, |this, _cx| match this.active_entry_kind {
LogKind::Trace => { LogKind::Trace => {
let log_view = log_view.clone(); let log_view = log_view.clone();
div().child( div().child(
PopoverMenu::new("lsp-trace-level-menu") PopoverMenu::new("lsp-trace-level-menu")
.anchor(Corner::TopLeft) .anchor(Corner::TopLeft)
.trigger( .trigger(trace_button)
Button::new(
"language_server_trace_level_selector",
"Trace level",
)
.icon(IconName::ChevronDown)
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
)
.menu({ .menu({
let log_view = log_view.clone(); let log_view = log_view.clone();
@ -1591,15 +1600,7 @@ impl Render for LspLogToolbarItemView {
div().child( div().child(
PopoverMenu::new("lsp-log-level-menu") PopoverMenu::new("lsp-log-level-menu")
.anchor(Corner::TopLeft) .anchor(Corner::TopLeft)
.trigger( .trigger(log_button)
Button::new(
"language_server_log_level_selector",
"Log level",
)
.icon(IconName::ChevronDown)
.icon_size(IconSize::Small)
.icon_color(Color::Muted),
)
.menu({ .menu({
let log_view = log_view.clone(); let log_view = log_view.clone();
@ -1656,11 +1657,11 @@ impl Render for LspLogToolbarItemView {
) )
} }
_ => div(), _ => div(),
}), })
), }),
) )
.child( .child(
Button::new("clear_log_button", "Clear").on_click(cx.listener( Button::new("clear_log_button", "Clear", cx).on_click(cx.listener(
|this, _, window, cx| { |this, _, window, cx| {
if let Some(log_view) = this.log_view.as_ref() { if let Some(log_view) = this.log_view.as_ref() {
log_view.update(cx, |log_view, cx| { log_view.update(cx, |log_view, cx| {

View file

@ -122,7 +122,7 @@ impl Render for StatusToast {
.child(Label::new(self.text.clone()).color(Color::Default)) .child(Label::new(self.text.clone()).color(Color::Default))
.when_some(self.action.as_ref(), |this, action| { .when_some(self.action.as_ref(), |this, action| {
this.child( this.child(
Button::new(action.id.clone(), action.label.clone()) Button::new(action.id.clone(), action.label.clone(), cx)
.tooltip(Tooltip::for_action_title( .tooltip(Tooltip::for_action_title(
action.label.clone(), action.label.clone(),
&toast::RunAction, &toast::RunAction,

View file

@ -80,7 +80,7 @@ fn render_privacy_card(tab_index: &mut isize, disabled: bool, cx: &mut App) -> i
.tooltip(move |_, cx| cx.new(|_| AiPrivacyTooltip::new()).into()), .tooltip(move |_, cx| cx.new(|_| AiPrivacyTooltip::new()).into()),
) )
.child( .child(
Button::new("learn_more", "Learn More") Button::new("learn_more", "Learn More", cx)
.style(ButtonStyle::Outlined) .style(ButtonStyle::Outlined)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
@ -206,7 +206,7 @@ fn render_llm_provider_card(
)) ))
.child(Divider::horizontal()) .child(Divider::horizontal())
.child( .child(
Button::new("agent_settings", "Add Many Others") Button::new("agent_settings", "Add Many Others", cx)
.size(ButtonSize::Large) .size(ButtonSize::Large)
.icon(IconName::Plus) .icon(IconName::Plus)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
@ -363,7 +363,7 @@ impl Render for AiConfigurationModal {
.section(Section::new().child(self.configuration_view.clone())) .section(Section::new().child(self.configuration_view.clone()))
.footer( .footer(
ModalFooter::new().end_slot( ModalFooter::new().end_slot(
Button::new("ai-onb-modal-Done", "Done") Button::new("ai-onb-modal-Done", "Done", cx)
.key_binding( .key_binding(
KeyBinding::for_action_in( KeyBinding::for_action_in(
&menu::Cancel, &menu::Cancel,

View file

@ -458,7 +458,7 @@ impl Onboarding {
) )
.into_any_element() .into_any_element()
} else { } else {
Button::new("sign_in", "Sign In") Button::new("sign_in", "Sign In", cx)
.full_width() .full_width()
.style(ButtonStyle::Outlined) .style(ButtonStyle::Outlined)
.size(ButtonSize::Medium) .size(ButtonSize::Medium)

View file

@ -235,7 +235,7 @@ impl Render for WelcomePage {
.border_color(cx.theme().colors().border.opacity(0.6)) .border_color(cx.theme().colors().border.opacity(0.6))
.border_dashed() .border_dashed()
.child( .child(
Button::new("welcome-exit", "Return to Setup") Button::new("welcome-exit", "Return to Setup", cx)
.tab_index(last_index as isize) .tab_index(last_index as isize)
.full_width() .full_width()
.label_size(LabelSize::XSmall) .label_size(LabelSize::XSmall)

View file

@ -50,10 +50,10 @@ impl RenderOnce for PanelTab {
} }
} }
pub fn panel_button(label: impl Into<SharedString>) -> ui::Button { pub fn panel_button(label: impl Into<SharedString>, app: &App) -> ui::Button {
let label = label.into(); let label = label.into();
let id = ElementId::Name(label.clone().to_lowercase().replace(' ', "_").into()); let id = ElementId::Name(label.clone().to_lowercase().replace(' ', "_").into());
ui::Button::new(id, label) ui::Button::new(id, label, app)
.label_size(ui::LabelSize::Small) .label_size(ui::LabelSize::Small)
.icon_size(ui::IconSize::Small) .icon_size(ui::IconSize::Small)
// TODO: Change this once we use on_surface_bg in button_like // TODO: Change this once we use on_surface_bg in button_like
@ -61,8 +61,8 @@ pub fn panel_button(label: impl Into<SharedString>) -> ui::Button {
.size(ui::ButtonSize::Compact) .size(ui::ButtonSize::Compact)
} }
pub fn panel_filled_button(label: impl Into<SharedString>) -> ui::Button { pub fn panel_filled_button(label: impl Into<SharedString>, app: &App) -> ui::Button {
panel_button(label).style(ui::ButtonStyle::Filled) panel_button(label, app).style(ui::ButtonStyle::Filled)
} }
pub fn panel_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton { pub fn panel_icon_button(id: impl Into<SharedString>, icon: IconName) -> ui::IconButton {

View file

@ -5531,7 +5531,7 @@ impl Render for ProjectPanel {
.p_4() .p_4()
.track_focus(&self.focus_handle(cx)) .track_focus(&self.focus_handle(cx))
.child( .child(
Button::new("open_project", "Open a project") Button::new("open_project", "Open a project", cx)
.full_width() .full_width()
.key_binding(KeyBinding::for_action_in( .key_binding(KeyBinding::for_action_in(
&OpenRecent::default(), &OpenRecent::default(),

View file

@ -185,7 +185,7 @@ impl Render for DisconnectedOverlay {
h_flex() h_flex()
.gap_2() .gap_2()
.child( .child(
Button::new("close-window", "Close Window") Button::new("close-window", "Close Window", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface) .layer(ElevationIndex::ModalSurface)
.on_click(cx.listener(move |_, _, window, _| { .on_click(cx.listener(move |_, _, window, _| {
@ -194,7 +194,7 @@ impl Render for DisconnectedOverlay {
) )
.when(can_reconnect, |el| { .when(can_reconnect, |el| {
el.child( el.child(
Button::new("reconnect", "Reconnect") Button::new("reconnect", "Reconnect", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.layer(ElevationIndex::ModalSurface) .layer(ElevationIndex::ModalSurface)
.icon(IconName::ArrowCircle) .icon(IconName::ArrowCircle)

View file

@ -479,7 +479,7 @@ impl PickerDelegate for RecentProjectsDelegate {
.border_t_1() .border_t_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.child( .child(
Button::new("remote", "Open Remote Folder") Button::new("remote", "Open Remote Folder", cx)
.key_binding(KeyBinding::for_action( .key_binding(KeyBinding::for_action(
&OpenRemote { &OpenRemote {
from_existing_connection: false, from_existing_connection: false,
@ -500,7 +500,7 @@ impl PickerDelegate for RecentProjectsDelegate {
}), }),
) )
.child( .child(
Button::new("local", "Open Local Folder") Button::new("local", "Open Local Folder", cx)
.key_binding(KeyBinding::for_action(&workspace::Open, window, cx)) .key_binding(KeyBinding::for_action(&workspace::Open, window, cx))
.on_click(|_, window, cx| { .on_click(|_, window, cx| {
window.dispatch_action(workspace::Open.boxed_clone(), cx) window.dispatch_action(workspace::Open.boxed_clone(), cx)

View file

@ -1094,7 +1094,7 @@ impl RemoteServerProjects {
.size(LabelSize::Small), .size(LabelSize::Small),
) )
.child( .child(
Button::new("learn-more", "Learn more…") Button::new("learn-more", "Learn more…", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.size(ButtonSize::None) .size(ButtonSize::None)
.color(Color::Accent) .color(Color::Accent)

View file

@ -234,7 +234,7 @@ impl PickerDelegate for KernelPickerDelegate {
.p_1() .p_1()
.gap_4() .gap_4()
.child( .child(
Button::new("kernel-docs", "Kernel Docs") Button::new("kernel-docs", "Kernel Docs", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_color(Color::Muted) .icon_color(Color::Muted)

View file

@ -655,7 +655,7 @@ impl Render for Session {
.as_ref() .as_ref()
.map(|info| info.language_info.name.clone()), .map(|info| info.language_info.name.clone()),
Some( Some(
Button::new("interrupt", "Interrupt") Button::new("interrupt", "Interrupt", cx)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.on_click(cx.listener(move |session, _, _, cx| { .on_click(cx.listener(move |session, _, _, cx| {
session.interrupt(cx); session.interrupt(cx);
@ -684,7 +684,7 @@ impl Render for Session {
.child(Label::new(self.kernel_specification.name())) .child(Label::new(self.kernel_specification.name()))
.children(status_text.map(|status_text| Label::new(format!("({status_text})")))) .children(status_text.map(|status_text| Label::new(format!("({status_text})"))))
.button( .button(
Button::new("shutdown", "Shutdown") Button::new("shutdown", "Shutdown", cx)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.disabled(self.kernel.is_shutting_down()) .disabled(self.kernel.is_shutting_down())
.on_click(cx.listener(move |session, _, window, cx| { .on_click(cx.listener(move |session, _, window, cx| {

View file

@ -1318,19 +1318,21 @@ impl Render for RulesLibrary {
"Create your first rule:", "Create your first rule:",
)) ))
.child( .child(
Button::new("create-rule", "New Rule") Button::new(
.full_width() "create-rule",
.key_binding( "New Rule",
KeyBinding::for_action( cx,
&NewRule, window, cx, )
), .full_width()
.key_binding(KeyBinding::for_action(
&NewRule, window, cx,
))
.on_click(|_, window, cx| {
window.dispatch_action(
NewRule.boxed_clone(),
cx,
) )
.on_click(|_, window, cx| { }),
window.dispatch_action(
NewRule.boxed_clone(),
cx,
)
}),
), ),
) )
.child(h_flex()), .child(h_flex()),

View file

@ -3,20 +3,23 @@ mod registrar;
use crate::{ use crate::{
FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex,
ToggleReplace, ToggleSelection, ToggleWholeWord, search_bar::render_nav_button, ToggleReplace, ToggleSelection, ToggleWholeWord,
search_bar::{
input_base_styles, render_action_button, render_text_input, toggle_replace_button,
},
}; };
use any_vec::AnyVec; use any_vec::AnyVec;
use anyhow::Context as _; use anyhow::Context as _;
use collections::HashMap; use collections::HashMap;
use editor::{ use editor::{
DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle, DisplayPoint, Editor, EditorSettings,
actions::{Backtab, Tab}, actions::{Backtab, Tab},
}; };
use futures::channel::oneshot; use futures::channel::oneshot;
use gpui::{ use gpui::{
Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _,
InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task,
Styled, Subscription, Task, TextStyle, Window, actions, div, Window, actions, div,
}; };
use language::{Language, LanguageRegistry}; use language::{Language, LanguageRegistry};
use project::{ use project::{
@ -27,7 +30,6 @@ use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use settings::Settings; use settings::Settings;
use std::sync::Arc; use std::sync::Arc;
use theme::ThemeSettings;
use zed_actions::outline::ToggleOutline; use zed_actions::outline::ToggleOutline;
use ui::{ use ui::{
@ -125,46 +127,6 @@ pub struct BufferSearchBar {
} }
impl BufferSearchBar { impl BufferSearchBar {
fn render_text_input(
&self,
editor: &Entity<Editor>,
color_override: Option<Color>,
cx: &mut Context<Self>,
) -> impl IntoElement {
let (color, use_syntax) = if editor.read(cx).read_only(cx) {
(cx.theme().colors().text_disabled, false)
} else {
match color_override {
Some(color_override) => (color_override.color(cx), false),
None => (cx.theme().colors().text, true),
}
};
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(1.3),
..TextStyle::default()
};
let mut editor_style = EditorStyle {
background: cx.theme().colors().toolbar_background,
local_player: cx.theme().players().local(),
text: text_style,
..EditorStyle::default()
};
if use_syntax {
editor_style.syntax = cx.theme().syntax().clone();
}
EditorElement::new(editor, editor_style)
}
pub fn query_editor_focused(&self) -> bool { pub fn query_editor_focused(&self) -> bool {
self.query_editor_focused self.query_editor_focused
} }
@ -185,7 +147,14 @@ impl Render for BufferSearchBar {
let hide_inline_icons = self.editor_needed_width let hide_inline_icons = self.editor_needed_width
> self.editor_scroll_handle.bounds().size.width - window.rem_size() * 6.; > self.editor_scroll_handle.bounds().size.width - window.rem_size() * 6.;
let supported_options = self.supported_options(cx); let workspace::searchable::SearchOptions {
case,
word,
regex,
replacement,
selection,
find_in_results,
} = self.supported_options(cx);
if self.query_editor.update(cx, |query_editor, _cx| { if self.query_editor.update(cx, |query_editor, _cx| {
query_editor.placeholder_text().is_none() query_editor.placeholder_text().is_none()
@ -220,268 +189,205 @@ impl Render for BufferSearchBar {
} }
}) })
.unwrap_or_else(|| "0/0".to_string()); .unwrap_or_else(|| "0/0".to_string());
let should_show_replace_input = self.replace_enabled && supported_options.replacement; let should_show_replace_input = self.replace_enabled && replacement;
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(window); let in_replace = self.replacement_editor.focus_handle(cx).is_focused(window);
let theme_colors = cx.theme().colors();
let query_border = if self.query_error.is_some() {
Color::Error.color(cx)
} else {
theme_colors.border
};
let replacement_border = theme_colors.border;
let container_width = window.viewport_size().width;
let input_width = SearchInputWidth::calc_width(container_width);
let input_base_styles =
|border_color| input_base_styles(border_color, |div| div.w(input_width));
let query_column = input_base_styles(query_border)
.id("editor-scroll")
.track_scroll(&self.editor_scroll_handle)
.child(render_text_input(&self.query_editor, color_override, cx))
.when(!hide_inline_icons, |div| {
div.child(
h_flex()
.gap_1()
.when(case, |div| {
div.child(SearchOptions::CASE_SENSITIVE.as_button(
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
focus_handle.clone(),
cx.listener(|this, _, window, cx| {
this.toggle_case_sensitive(&ToggleCaseSensitive, window, cx)
}),
))
})
.when(word, |div| {
div.child(SearchOptions::WHOLE_WORD.as_button(
self.search_options.contains(SearchOptions::WHOLE_WORD),
focus_handle.clone(),
cx.listener(|this, _, window, cx| {
this.toggle_whole_word(&ToggleWholeWord, window, cx)
}),
))
})
.when(regex, |div| {
div.child(SearchOptions::REGEX.as_button(
self.search_options.contains(SearchOptions::REGEX),
focus_handle.clone(),
cx.listener(|this, _, window, cx| {
this.toggle_regex(&ToggleRegex, window, cx)
}),
))
}),
)
});
let mode_column = h_flex()
.gap_1()
.min_w_64()
.when(replacement, |this| {
this.child(toggle_replace_button(
"buffer-search-bar-toggle-replace-button",
focus_handle.clone(),
self.replace_enabled,
cx.listener(|this, _: &ClickEvent, window, cx| {
this.toggle_replace(&ToggleReplace, window, cx);
}),
))
})
.when(selection, |this| {
this.child(
IconButton::new(
"buffer-search-bar-toggle-search-selection-button",
IconName::Quote,
)
.style(ButtonStyle::Subtle)
.shape(IconButtonShape::Square)
.when(self.selection_search_enabled, |button| {
button.style(ButtonStyle::Filled)
})
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
this.toggle_selection(&ToggleSelection, window, cx);
}))
.toggle_state(self.selection_search_enabled)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Toggle Search Selection",
&ToggleSelection,
&focus_handle,
window,
cx,
)
}
}),
)
})
.when(!find_in_results, |el| {
let query_focus = self.query_editor.focus_handle(cx);
let matches_column = h_flex()
.pl_2()
.ml_2()
.border_l_1()
.border_color(theme_colors.border_variant)
.child(render_action_button(
"buffer-search-nav-button",
ui::IconName::ChevronLeft,
self.active_match_index.is_some(),
"Select Previous Match",
&SelectPreviousMatch,
query_focus.clone(),
))
.child(render_action_button(
"buffer-search-nav-button",
ui::IconName::ChevronRight,
self.active_match_index.is_some(),
"Select Next Match",
&SelectNextMatch,
query_focus.clone(),
))
.when(!narrow_mode, |this| {
this.child(div().ml_2().min_w(rems_from_px(40.)).child(
Label::new(match_text).size(LabelSize::Small).color(
if self.active_match_index.is_some() {
Color::Default
} else {
Color::Disabled
},
),
))
});
el.child(render_action_button(
"buffer-search-nav-button",
IconName::SelectAll,
true,
"Select All Matches",
&SelectAllMatches,
query_focus,
))
.child(matches_column)
})
.when(find_in_results, |el| {
el.child(render_action_button(
"buffer-search",
IconName::Close,
true,
"Close Search Bar",
&Dismiss,
focus_handle.clone(),
))
});
let search_line = h_flex()
.w_full()
.gap_2()
.when(find_in_results, |el| {
el.child(Label::new("Find in results").color(Color::Hint))
})
.child(query_column)
.child(mode_column);
let replace_line =
should_show_replace_input.then(|| {
let replace_column = input_base_styles(replacement_border)
.child(render_text_input(&self.replacement_editor, None, cx));
let focus_handle = self.replacement_editor.read(cx).focus_handle(cx);
let replace_actions = h_flex()
.min_w_64()
.gap_1()
.child(render_action_button(
"buffer-search-replace-button",
IconName::ReplaceNext,
true,
"Replace Next Match",
&ReplaceNext,
focus_handle.clone(),
))
.child(render_action_button(
"buffer-search-replace-button",
IconName::ReplaceAll,
true,
"Replace All Matches",
&ReplaceAll,
focus_handle,
));
h_flex()
.w_full()
.gap_2()
.child(replace_column)
.child(replace_actions)
});
let mut key_context = KeyContext::new_with_defaults(); let mut key_context = KeyContext::new_with_defaults();
key_context.add("BufferSearchBar"); key_context.add("BufferSearchBar");
if in_replace { if in_replace {
key_context.add("in_replace"); key_context.add("in_replace");
} }
let query_border = if self.query_error.is_some() {
Color::Error.color(cx)
} else {
cx.theme().colors().border
};
let replacement_border = cx.theme().colors().border;
let container_width = window.viewport_size().width;
let input_width = SearchInputWidth::calc_width(container_width);
let input_base_styles = |border_color| {
h_flex()
.min_w_32()
.w(input_width)
.h_8()
.pl_2()
.pr_1()
.py_1()
.border_1()
.border_color(border_color)
.rounded_lg()
};
let search_line = h_flex()
.gap_2()
.when(supported_options.find_in_results, |el| {
el.child(Label::new("Find in results").color(Color::Hint))
})
.child(
input_base_styles(query_border)
.id("editor-scroll")
.track_scroll(&self.editor_scroll_handle)
.child(self.render_text_input(&self.query_editor, color_override, cx))
.when(!hide_inline_icons, |div| {
div.child(
h_flex()
.gap_1()
.children(supported_options.case.then(|| {
self.render_search_option_button(
SearchOptions::CASE_SENSITIVE,
focus_handle.clone(),
cx.listener(|this, _, window, cx| {
this.toggle_case_sensitive(
&ToggleCaseSensitive,
window,
cx,
)
}),
)
}))
.children(supported_options.word.then(|| {
self.render_search_option_button(
SearchOptions::WHOLE_WORD,
focus_handle.clone(),
cx.listener(|this, _, window, cx| {
this.toggle_whole_word(&ToggleWholeWord, window, cx)
}),
)
}))
.children(supported_options.regex.then(|| {
self.render_search_option_button(
SearchOptions::REGEX,
focus_handle.clone(),
cx.listener(|this, _, window, cx| {
this.toggle_regex(&ToggleRegex, window, cx)
}),
)
})),
)
}),
)
.child(
h_flex()
.gap_1()
.min_w_64()
.when(supported_options.replacement, |this| {
this.child(
IconButton::new(
"buffer-search-bar-toggle-replace-button",
IconName::Replace,
)
.style(ButtonStyle::Subtle)
.shape(IconButtonShape::Square)
.when(self.replace_enabled, |button| {
button.style(ButtonStyle::Filled)
})
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
this.toggle_replace(&ToggleReplace, window, cx);
}))
.toggle_state(self.replace_enabled)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Toggle Replace",
&ToggleReplace,
&focus_handle,
window,
cx,
)
}
}),
)
})
.when(supported_options.selection, |this| {
this.child(
IconButton::new(
"buffer-search-bar-toggle-search-selection-button",
IconName::Quote,
)
.style(ButtonStyle::Subtle)
.shape(IconButtonShape::Square)
.when(self.selection_search_enabled, |button| {
button.style(ButtonStyle::Filled)
})
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
this.toggle_selection(&ToggleSelection, window, cx);
}))
.toggle_state(self.selection_search_enabled)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Toggle Search Selection",
&ToggleSelection,
&focus_handle,
window,
cx,
)
}
}),
)
})
.when(!supported_options.find_in_results, |el| {
el.child(
IconButton::new("select-all", ui::IconName::SelectAll)
.on_click(|_, window, cx| {
window.dispatch_action(SelectAllMatches.boxed_clone(), cx)
})
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Select All Matches",
&SelectAllMatches,
&focus_handle,
window,
cx,
)
}
}),
)
.child(
h_flex()
.pl_2()
.ml_1()
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.child(render_nav_button(
ui::IconName::ChevronLeft,
self.active_match_index.is_some(),
"Select Previous Match",
&SelectPreviousMatch,
focus_handle.clone(),
))
.child(render_nav_button(
ui::IconName::ChevronRight,
self.active_match_index.is_some(),
"Select Next Match",
&SelectNextMatch,
focus_handle.clone(),
)),
)
.when(!narrow_mode, |this| {
this.child(h_flex().ml_2().min_w(rems_from_px(40.)).child(
Label::new(match_text).size(LabelSize::Small).color(
if self.active_match_index.is_some() {
Color::Default
} else {
Color::Disabled
},
),
))
})
})
.when(supported_options.find_in_results, |el| {
el.child(
IconButton::new(SharedString::from("Close"), IconName::Close)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
Tooltip::for_action("Close Search Bar", &Dismiss, window, cx)
})
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
this.dismiss(&Dismiss, window, cx)
})),
)
}),
);
let replace_line = should_show_replace_input.then(|| {
h_flex()
.gap_2()
.child(
input_base_styles(replacement_border).child(self.render_text_input(
&self.replacement_editor,
None,
cx,
)),
)
.child(
h_flex()
.min_w_64()
.gap_1()
.child(
IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Replace Next Match",
&ReplaceNext,
&focus_handle,
window,
cx,
)
}
})
.on_click(cx.listener(|this, _, window, cx| {
this.replace_next(&ReplaceNext, window, cx)
})),
)
.child(
IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
.shape(IconButtonShape::Square)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Replace All Matches",
&ReplaceAll,
&focus_handle,
window,
cx,
)
}
})
.on_click(cx.listener(|this, _, window, cx| {
this.replace_all(&ReplaceAll, window, cx)
})),
),
)
});
let query_error_line = self.query_error.as_ref().map(|error| { let query_error_line = self.query_error.as_ref().map(|error| {
Label::new(error) Label::new(error)
@ -491,10 +397,26 @@ impl Render for BufferSearchBar {
.ml_2() .ml_2()
}); });
let search_line =
h_flex()
.relative()
.child(search_line)
.when(!narrow_mode && !find_in_results, |div| {
div.child(h_flex().absolute().right_0().child(render_action_button(
"buffer-search",
IconName::Close,
true,
"Close Search Bar",
&Dismiss,
focus_handle.clone(),
)))
.w_full()
});
v_flex() v_flex()
.id("buffer_search") .id("buffer_search")
.gap_2() .gap_2()
.py(px(1.0)) .py(px(1.0))
.w_full()
.track_scroll(&self.scroll_handle) .track_scroll(&self.scroll_handle)
.key_context(key_context) .key_context(key_context)
.capture_action(cx.listener(Self::tab)) .capture_action(cx.listener(Self::tab))
@ -509,43 +431,26 @@ impl Render for BufferSearchBar {
active_searchable_item.relay_action(Box::new(ToggleOutline), window, cx); active_searchable_item.relay_action(Box::new(ToggleOutline), window, cx);
} }
})) }))
.when(self.supported_options(cx).replacement, |this| { .when(replacement, |this| {
this.on_action(cx.listener(Self::toggle_replace)) this.on_action(cx.listener(Self::toggle_replace))
.when(in_replace, |this| { .when(in_replace, |this| {
this.on_action(cx.listener(Self::replace_next)) this.on_action(cx.listener(Self::replace_next))
.on_action(cx.listener(Self::replace_all)) .on_action(cx.listener(Self::replace_all))
}) })
}) })
.when(self.supported_options(cx).case, |this| { .when(case, |this| {
this.on_action(cx.listener(Self::toggle_case_sensitive)) this.on_action(cx.listener(Self::toggle_case_sensitive))
}) })
.when(self.supported_options(cx).word, |this| { .when(word, |this| {
this.on_action(cx.listener(Self::toggle_whole_word)) this.on_action(cx.listener(Self::toggle_whole_word))
}) })
.when(self.supported_options(cx).regex, |this| { .when(regex, |this| {
this.on_action(cx.listener(Self::toggle_regex)) this.on_action(cx.listener(Self::toggle_regex))
}) })
.when(self.supported_options(cx).selection, |this| { .when(selection, |this| {
this.on_action(cx.listener(Self::toggle_selection)) this.on_action(cx.listener(Self::toggle_selection))
}) })
.child(h_flex().relative().child(search_line.w_full()).when( .child(search_line)
!narrow_mode && !supported_options.find_in_results,
|div| {
div.child(
h_flex().absolute().right_0().child(
IconButton::new(SharedString::from("Close"), IconName::Close)
.shape(IconButtonShape::Square)
.tooltip(move |window, cx| {
Tooltip::for_action("Close Search Bar", &Dismiss, window, cx)
})
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
this.dismiss(&Dismiss, window, cx)
})),
),
)
.w_full()
},
))
.children(query_error_line) .children(query_error_line)
.children(replace_line) .children(replace_line)
} }
@ -792,7 +697,7 @@ impl BufferSearchBar {
active_editor.search_bar_visibility_changed(false, window, cx); active_editor.search_bar_visibility_changed(false, window, cx);
active_editor.toggle_filtered_search_ranges(false, window, cx); active_editor.toggle_filtered_search_ranges(false, window, cx);
let handle = active_editor.item_focus_handle(cx); let handle = active_editor.item_focus_handle(cx);
self.focus(&handle, window, cx); self.focus(&handle, window);
} }
cx.emit(Event::UpdateLocation); cx.emit(Event::UpdateLocation);
cx.emit(ToolbarItemEvent::ChangeLocation( cx.emit(ToolbarItemEvent::ChangeLocation(
@ -948,7 +853,7 @@ impl BufferSearchBar {
} }
pub fn focus_replace(&mut self, window: &mut Window, cx: &mut Context<Self>) { pub fn focus_replace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
self.focus(&self.replacement_editor.focus_handle(cx), window, cx); self.focus(&self.replacement_editor.focus_handle(cx), window);
cx.notify(); cx.notify();
} }
@ -975,16 +880,6 @@ impl BufferSearchBar {
self.update_matches(!updated, window, cx) self.update_matches(!updated, window, cx)
} }
fn render_search_option_button<Action: Fn(&ClickEvent, &mut Window, &mut App) + 'static>(
&self,
option: SearchOptions,
focus_handle: FocusHandle,
action: Action,
) -> impl IntoElement + use<Action> {
let is_active = self.search_options.contains(option);
option.as_button(is_active, focus_handle, action)
}
pub fn focus_editor(&mut self, _: &FocusEditor, window: &mut Window, cx: &mut Context<Self>) { pub fn focus_editor(&mut self, _: &FocusEditor, window: &mut Window, cx: &mut Context<Self>) {
if let Some(active_editor) = self.active_searchable_item.as_ref() { if let Some(active_editor) = self.active_searchable_item.as_ref() {
let handle = active_editor.item_focus_handle(cx); let handle = active_editor.item_focus_handle(cx);
@ -1400,28 +1295,32 @@ impl BufferSearchBar {
} }
fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) { fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
// Search -> Replace -> Editor self.cycle_field(Direction::Next, window, cx);
let focus_handle = if self.replace_enabled && self.query_editor_focused {
self.replacement_editor.focus_handle(cx)
} else if let Some(item) = self.active_searchable_item.as_ref() {
item.item_focus_handle(cx)
} else {
return;
};
self.focus(&focus_handle, window, cx);
cx.stop_propagation();
} }
fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) { fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
// Search -> Replace -> Search self.cycle_field(Direction::Prev, window, cx);
let focus_handle = if self.replace_enabled && self.query_editor_focused { }
self.replacement_editor.focus_handle(cx) fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>) {
} else if self.replacement_editor_focused { let mut handles = vec![self.query_editor.focus_handle(cx)];
self.query_editor.focus_handle(cx) if self.replace_enabled {
} else { handles.push(self.replacement_editor.focus_handle(cx));
return; }
if let Some(item) = self.active_searchable_item.as_ref() {
handles.push(item.item_focus_handle(cx));
}
let current_index = match handles.iter().position(|focus| focus.is_focused(window)) {
Some(index) => index,
None => return,
}; };
self.focus(&focus_handle, window, cx);
let new_index = match direction {
Direction::Next => (current_index + 1) % handles.len(),
Direction::Prev if current_index == 0 => handles.len() - 1,
Direction::Prev => (current_index - 1) % handles.len(),
};
let next_focus_handle = &handles[new_index];
self.focus(next_focus_handle, window);
cx.stop_propagation(); cx.stop_propagation();
} }
@ -1469,10 +1368,8 @@ impl BufferSearchBar {
} }
} }
fn focus(&self, handle: &gpui::FocusHandle, window: &mut Window, cx: &mut Context<Self>) { fn focus(&self, handle: &gpui::FocusHandle, window: &mut Window) {
cx.on_next_frame(window, |_, window, _| { window.invalidate_character_coordinates();
window.invalidate_character_coordinates();
});
window.focus(handle); window.focus(handle);
} }
@ -1484,7 +1381,7 @@ impl BufferSearchBar {
} else { } else {
self.query_editor.focus_handle(cx) self.query_editor.focus_handle(cx)
}; };
self.focus(&handle, window, cx); self.focus(&handle, window);
cx.notify(); cx.notify();
} }
} }

View file

@ -1,20 +1,25 @@
use crate::{ use crate::{
BufferSearchBar, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, BufferSearchBar, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored, SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored,
ToggleRegex, ToggleReplace, ToggleWholeWord, buffer_search::Deploy, ToggleRegex, ToggleReplace, ToggleWholeWord,
buffer_search::Deploy,
search_bar::{
input_base_styles, render_action_button, render_text_input, toggle_replace_button,
},
}; };
use anyhow::Context as _; use anyhow::Context as _;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use editor::{ use editor::{
Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MAX_TAB_TITLE_LEN, Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, SelectionEffects,
MultiBuffer, SelectionEffects, actions::SelectAll, items::active_match_index, actions::{Backtab, SelectAll, Tab},
items::active_match_index,
}; };
use futures::{StreamExt, stream::FuturesOrdered}; use futures::{StreamExt, stream::FuturesOrdered};
use gpui::{ use gpui::{
Action, AnyElement, AnyView, App, Axis, Context, Entity, EntityId, EventEmitter, FocusHandle, Action, AnyElement, AnyView, App, Axis, Context, Entity, EntityId, EventEmitter, FocusHandle,
Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyContext, ParentElement, Point, Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyContext, ParentElement, Point,
Render, SharedString, Styled, Subscription, Task, TextStyle, UpdateGlobal, WeakEntity, Window, Render, SharedString, Styled, Subscription, Task, UpdateGlobal, WeakEntity, Window, actions,
actions, div, div,
}; };
use language::{Buffer, Language}; use language::{Buffer, Language};
use menu::Confirm; use menu::Confirm;
@ -32,7 +37,6 @@ use std::{
pin::pin, pin::pin,
sync::Arc, sync::Arc,
}; };
use theme::ThemeSettings;
use ui::{ use ui::{
Icon, IconButton, IconButtonShape, IconName, KeyBinding, Label, LabelCommon, LabelSize, Icon, IconButton, IconButtonShape, IconName, KeyBinding, Label, LabelCommon, LabelSize,
Toggleable, Tooltip, h_flex, prelude::*, utils::SearchInputWidth, v_flex, Toggleable, Tooltip, h_flex, prelude::*, utils::SearchInputWidth, v_flex,
@ -1435,7 +1439,7 @@ impl ProjectSearchView {
.mb_2(), .mb_2(),
) )
.child( .child(
Button::new("filter-paths", "Include/exclude specific paths") Button::new("filter-paths", "Include/exclude specific paths", cx)
.icon(IconName::Filter) .icon(IconName::Filter)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -1450,7 +1454,7 @@ impl ProjectSearchView {
}), }),
) )
.child( .child(
Button::new("find-replace", "Find and replace") Button::new("find-replace", "Find and replace", cx)
.icon(IconName::Replace) .icon(IconName::Replace)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -1465,7 +1469,7 @@ impl ProjectSearchView {
}), }),
) )
.child( .child(
Button::new("regex", "Match with regex") Button::new("regex", "Match with regex", cx)
.icon(IconName::Regex) .icon(IconName::Regex)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -1480,7 +1484,7 @@ impl ProjectSearchView {
}), }),
) )
.child( .child(
Button::new("match-case", "Match case") Button::new("match-case", "Match case", cx)
.icon(IconName::CaseSensitive) .icon(IconName::CaseSensitive)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -1495,7 +1499,7 @@ impl ProjectSearchView {
}), }),
) )
.child( .child(
Button::new("match-whole-words", "Match whole words") Button::new("match-whole-words", "Match whole words", cx)
.icon(IconName::WholeWord) .icon(IconName::WholeWord)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -1610,16 +1614,11 @@ impl ProjectSearchBar {
} }
} }
fn tab(&mut self, _: &editor::actions::Tab, window: &mut Window, cx: &mut Context<Self>) { fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
self.cycle_field(Direction::Next, window, cx); self.cycle_field(Direction::Next, window, cx);
} }
fn backtab( fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
&mut self,
_: &editor::actions::Backtab,
window: &mut Window,
cx: &mut Context<Self>,
) {
self.cycle_field(Direction::Prev, window, cx); self.cycle_field(Direction::Prev, window, cx);
} }
@ -1634,29 +1633,22 @@ impl ProjectSearchBar {
fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>) { fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>) {
let active_project_search = match &self.active_project_search { let active_project_search = match &self.active_project_search {
Some(active_project_search) => active_project_search, Some(active_project_search) => active_project_search,
None => return,
None => {
return;
}
}; };
active_project_search.update(cx, |project_view, cx| { active_project_search.update(cx, |project_view, cx| {
let mut views = vec![&project_view.query_editor]; let mut views = vec![project_view.query_editor.focus_handle(cx)];
if project_view.replace_enabled { if project_view.replace_enabled {
views.push(&project_view.replacement_editor); views.push(project_view.replacement_editor.focus_handle(cx));
} }
if project_view.filters_enabled { if project_view.filters_enabled {
views.extend([ views.extend([
&project_view.included_files_editor, project_view.included_files_editor.focus_handle(cx),
&project_view.excluded_files_editor, project_view.excluded_files_editor.focus_handle(cx),
]); ]);
} }
let current_index = match views let current_index = match views.iter().position(|focus| focus.is_focused(window)) {
.iter() Some(index) => index,
.enumerate()
.find(|(_, editor)| editor.focus_handle(cx).is_focused(window))
{
Some((index, _)) => index,
None => return, None => return,
}; };
@ -1665,8 +1657,8 @@ impl ProjectSearchBar {
Direction::Prev if current_index == 0 => views.len() - 1, Direction::Prev if current_index == 0 => views.len() - 1,
Direction::Prev => (current_index - 1) % views.len(), Direction::Prev => (current_index - 1) % views.len(),
}; };
let next_focus_handle = views[new_index].focus_handle(cx); let next_focus_handle = &views[new_index];
window.focus(&next_focus_handle); window.focus(next_focus_handle);
cx.stop_propagation(); cx.stop_propagation();
}); });
} }
@ -1915,37 +1907,6 @@ impl ProjectSearchBar {
}) })
} }
} }
fn render_text_input(&self, editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement {
let (color, use_syntax) = if editor.read(cx).read_only(cx) {
(cx.theme().colors().text_disabled, false)
} else {
(cx.theme().colors().text, true)
};
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(1.3),
..TextStyle::default()
};
let mut editor_style = EditorStyle {
background: cx.theme().colors().toolbar_background,
local_player: cx.theme().players().local(),
text: text_style,
..EditorStyle::default()
};
if use_syntax {
editor_style.syntax = cx.theme().syntax().clone();
}
EditorElement::new(editor, editor_style)
}
} }
impl Render for ProjectSearchBar { impl Render for ProjectSearchBar {
@ -1965,21 +1926,41 @@ impl Render for ProjectSearchBar {
} }
let input_base_styles = |base_style: BaseStyle, panel: InputPanel| { let input_base_styles = |base_style: BaseStyle, panel: InputPanel| {
h_flex() input_base_styles(search.border_color_for(panel, cx), |div| match base_style {
.min_w_32() BaseStyle::SingleInput => div.w(input_width),
.map(|div| match base_style { BaseStyle::MultipleInputs => div.flex_grow(),
BaseStyle::SingleInput => div.w(input_width), })
BaseStyle::MultipleInputs => div.flex_grow(),
})
.h_8()
.pl_2()
.pr_1()
.py_1()
.border_1()
.border_color(search.border_color_for(panel, cx))
.rounded_lg()
}; };
let project_search = search.entity.read(cx);
let limit_reached = project_search.limit_reached;
let color_override = match (
project_search.no_results,
&project_search.active_query,
&project_search.last_search_query_text,
) {
(Some(true), Some(q), Some(p)) if q.as_str() == p => Some(Color::Error),
_ => None,
};
let match_text = search
.active_match_index
.and_then(|index| {
let index = index + 1;
let match_quantity = project_search.match_ranges.len();
if match_quantity > 0 {
debug_assert!(match_quantity >= index);
if limit_reached {
Some(format!("{index}/{match_quantity}+"))
} else {
Some(format!("{index}/{match_quantity}"))
}
} else {
None
}
})
.unwrap_or_else(|| "0/0".to_string());
let query_column = input_base_styles(BaseStyle::SingleInput, InputPanel::Query) let query_column = input_base_styles(BaseStyle::SingleInput, InputPanel::Query)
.on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx))) .on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx)))
.on_action(cx.listener(|this, action, window, cx| { .on_action(cx.listener(|this, action, window, cx| {
@ -1988,7 +1969,7 @@ impl Render for ProjectSearchBar {
.on_action( .on_action(
cx.listener(|this, action, window, cx| this.next_history_query(action, window, cx)), cx.listener(|this, action, window, cx| this.next_history_query(action, window, cx)),
) )
.child(self.render_text_input(&search.query_editor, cx)) .child(render_text_input(&search.query_editor, color_override, cx))
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
@ -2017,6 +1998,7 @@ impl Render for ProjectSearchBar {
let mode_column = h_flex() let mode_column = h_flex()
.gap_1() .gap_1()
.min_w_64()
.child( .child(
IconButton::new("project-search-filter-button", IconName::Filter) IconButton::new("project-search-filter-button", IconName::Filter)
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
@ -2045,109 +2027,46 @@ impl Render for ProjectSearchBar {
} }
}), }),
) )
.child( .child(toggle_replace_button(
IconButton::new("project-search-toggle-replace", IconName::Replace) "project-search-toggle-replace",
.shape(IconButtonShape::Square) focus_handle.clone(),
.on_click(cx.listener(|this, _, window, cx| { self.active_project_search
this.toggle_replace(&ToggleReplace, window, cx); .as_ref()
})) .map(|search| search.read(cx).replace_enabled)
.toggle_state( .unwrap_or_default(),
self.active_project_search cx.listener(|this, _, window, cx| {
.as_ref() this.toggle_replace(&ToggleReplace, window, cx);
.map(|search| search.read(cx).replace_enabled) }),
.unwrap_or_default(), ));
)
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Toggle Replace",
&ToggleReplace,
&focus_handle,
window,
cx,
)
}
}),
);
let limit_reached = search.entity.read(cx).limit_reached; let query_focus = search.query_editor.focus_handle(cx);
let match_text = search
.active_match_index
.and_then(|index| {
let index = index + 1;
let match_quantity = search.entity.read(cx).match_ranges.len();
if match_quantity > 0 {
debug_assert!(match_quantity >= index);
if limit_reached {
Some(format!("{index}/{match_quantity}+"))
} else {
Some(format!("{index}/{match_quantity}"))
}
} else {
None
}
})
.unwrap_or_else(|| "0/0".to_string());
let matches_column = h_flex() let matches_column = h_flex()
.pl_2() .pl_2()
.ml_2() .ml_2()
.border_l_1() .border_l_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.child( .child(render_action_button(
IconButton::new("project-search-prev-match", IconName::ChevronLeft) "project-search-nav-button",
.shape(IconButtonShape::Square) IconName::ChevronLeft,
.disabled(search.active_match_index.is_none()) search.active_match_index.is_some(),
.on_click(cx.listener(|this, _, window, cx| { "Select Previous Match",
if let Some(search) = this.active_project_search.as_ref() { &SelectPreviousMatch,
search.update(cx, |this, cx| { query_focus.clone(),
this.select_match(Direction::Prev, window, cx); ))
}) .child(render_action_button(
} "project-search-nav-button",
})) IconName::ChevronRight,
.tooltip({ search.active_match_index.is_some(),
let focus_handle = focus_handle.clone(); "Select Next Match",
move |window, cx| { &SelectNextMatch,
Tooltip::for_action_in( query_focus,
"Go To Previous Match", ))
&SelectPreviousMatch,
&focus_handle,
window,
cx,
)
}
}),
)
.child(
IconButton::new("project-search-next-match", IconName::ChevronRight)
.shape(IconButtonShape::Square)
.disabled(search.active_match_index.is_none())
.on_click(cx.listener(|this, _, window, cx| {
if let Some(search) = this.active_project_search.as_ref() {
search.update(cx, |this, cx| {
this.select_match(Direction::Next, window, cx);
})
}
}))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Go To Next Match",
&SelectNextMatch,
&focus_handle,
window,
cx,
)
}
}),
)
.child( .child(
div() div()
.id("matches") .id("matches")
.ml_1() .ml_2()
.min_w(rems_from_px(40.))
.child(Label::new(match_text).size(LabelSize::Small).color( .child(Label::new(match_text).size(LabelSize::Small).color(
if search.active_match_index.is_some() { if search.active_match_index.is_some() {
Color::Default Color::Default
@ -2170,62 +2089,29 @@ impl Render for ProjectSearchBar {
let replace_line = search.replace_enabled.then(|| { let replace_line = search.replace_enabled.then(|| {
let replace_column = input_base_styles(BaseStyle::SingleInput, InputPanel::Replacement) let replace_column = input_base_styles(BaseStyle::SingleInput, InputPanel::Replacement)
.child(self.render_text_input(&search.replacement_editor, cx)); .child(render_text_input(&search.replacement_editor, None, cx));
let focus_handle = search.replacement_editor.read(cx).focus_handle(cx); let focus_handle = search.replacement_editor.read(cx).focus_handle(cx);
let replace_actions = let replace_actions = h_flex()
h_flex() .min_w_64()
.min_w_64() .gap_1()
.gap_1() .child(render_action_button(
.when(search.replace_enabled, |this| { "project-search-replace-button",
this.child( IconName::ReplaceNext,
IconButton::new("project-search-replace-next", IconName::ReplaceNext) true,
.shape(IconButtonShape::Square) "Replace Next Match",
.on_click(cx.listener(|this, _, window, cx| { &ReplaceNext,
if let Some(search) = this.active_project_search.as_ref() { focus_handle.clone(),
search.update(cx, |this, cx| { ))
this.replace_next(&ReplaceNext, window, cx); .child(render_action_button(
}) "project-search-replace-button",
} IconName::ReplaceAll,
})) true,
.tooltip({ "Replace All Matches",
let focus_handle = focus_handle.clone(); &ReplaceAll,
move |window, cx| { focus_handle,
Tooltip::for_action_in( ));
"Replace Next Match",
&ReplaceNext,
&focus_handle,
window,
cx,
)
}
}),
)
.child(
IconButton::new("project-search-replace-all", IconName::ReplaceAll)
.shape(IconButtonShape::Square)
.on_click(cx.listener(|this, _, window, cx| {
if let Some(search) = this.active_project_search.as_ref() {
search.update(cx, |this, cx| {
this.replace_all(&ReplaceAll, window, cx);
})
}
}))
.tooltip({
let focus_handle = focus_handle.clone();
move |window, cx| {
Tooltip::for_action_in(
"Replace All Matches",
&ReplaceAll,
&focus_handle,
window,
cx,
)
}
}),
)
});
h_flex() h_flex()
.w_full() .w_full()
@ -2235,6 +2121,45 @@ impl Render for ProjectSearchBar {
}); });
let filter_line = search.filters_enabled.then(|| { let filter_line = search.filters_enabled.then(|| {
let include = input_base_styles(BaseStyle::MultipleInputs, InputPanel::Include)
.on_action(cx.listener(|this, action, window, cx| {
this.previous_history_query(action, window, cx)
}))
.on_action(cx.listener(|this, action, window, cx| {
this.next_history_query(action, window, cx)
}))
.child(render_text_input(&search.included_files_editor, None, cx));
let exclude = input_base_styles(BaseStyle::MultipleInputs, InputPanel::Exclude)
.on_action(cx.listener(|this, action, window, cx| {
this.previous_history_query(action, window, cx)
}))
.on_action(cx.listener(|this, action, window, cx| {
this.next_history_query(action, window, cx)
}))
.child(render_text_input(&search.excluded_files_editor, None, cx));
let mode_column = h_flex()
.gap_1()
.min_w_64()
.child(
IconButton::new("project-search-opened-only", IconName::FolderSearch)
.shape(IconButtonShape::Square)
.toggle_state(self.is_opened_only_enabled(cx))
.tooltip(Tooltip::text("Only Search Open Files"))
.on_click(cx.listener(|this, _, window, cx| {
this.toggle_opened_only(window, cx);
})),
)
.child(
SearchOptions::INCLUDE_IGNORED.as_button(
search
.search_options
.contains(SearchOptions::INCLUDE_IGNORED),
focus_handle.clone(),
cx.listener(|this, _, window, cx| {
this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, window, cx);
}),
),
);
h_flex() h_flex()
.w_full() .w_full()
.gap_2() .gap_2()
@ -2242,62 +2167,14 @@ impl Render for ProjectSearchBar {
h_flex() h_flex()
.gap_2() .gap_2()
.w(input_width) .w(input_width)
.child( .child(include)
input_base_styles(BaseStyle::MultipleInputs, InputPanel::Include) .child(exclude),
.on_action(cx.listener(|this, action, window, cx| {
this.previous_history_query(action, window, cx)
}))
.on_action(cx.listener(|this, action, window, cx| {
this.next_history_query(action, window, cx)
}))
.child(self.render_text_input(&search.included_files_editor, cx)),
)
.child(
input_base_styles(BaseStyle::MultipleInputs, InputPanel::Exclude)
.on_action(cx.listener(|this, action, window, cx| {
this.previous_history_query(action, window, cx)
}))
.on_action(cx.listener(|this, action, window, cx| {
this.next_history_query(action, window, cx)
}))
.child(self.render_text_input(&search.excluded_files_editor, cx)),
),
)
.child(
h_flex()
.min_w_64()
.gap_1()
.child(
IconButton::new("project-search-opened-only", IconName::FolderSearch)
.shape(IconButtonShape::Square)
.toggle_state(self.is_opened_only_enabled(cx))
.tooltip(Tooltip::text("Only Search Open Files"))
.on_click(cx.listener(|this, _, window, cx| {
this.toggle_opened_only(window, cx);
})),
)
.child(
SearchOptions::INCLUDE_IGNORED.as_button(
search
.search_options
.contains(SearchOptions::INCLUDE_IGNORED),
focus_handle.clone(),
cx.listener(|this, _, window, cx| {
this.toggle_search_option(
SearchOptions::INCLUDE_IGNORED,
window,
cx,
);
}),
),
),
) )
.child(mode_column)
}); });
let mut key_context = KeyContext::default(); let mut key_context = KeyContext::default();
key_context.add("ProjectSearchBar"); key_context.add("ProjectSearchBar");
if search if search
.replacement_editor .replacement_editor
.focus_handle(cx) .focus_handle(cx)
@ -2315,7 +2192,9 @@ impl Render for ProjectSearchBar {
}); });
v_flex() v_flex()
.gap_2()
.py(px(1.0)) .py(px(1.0))
.w_full()
.key_context(key_context) .key_context(key_context)
.on_action(cx.listener(|this, _: &ToggleFocus, window, cx| { .on_action(cx.listener(|this, _: &ToggleFocus, window, cx| {
this.move_focus_to_results(window, cx) this.move_focus_to_results(window, cx)
@ -2323,14 +2202,8 @@ impl Render for ProjectSearchBar {
.on_action(cx.listener(|this, _: &ToggleFilters, window, cx| { .on_action(cx.listener(|this, _: &ToggleFilters, window, cx| {
this.toggle_filters(window, cx); this.toggle_filters(window, cx);
})) }))
.capture_action(cx.listener(|this, action, window, cx| { .capture_action(cx.listener(Self::tab))
this.tab(action, window, cx); .capture_action(cx.listener(Self::backtab))
cx.stop_propagation();
}))
.capture_action(cx.listener(|this, action, window, cx| {
this.backtab(action, window, cx);
cx.stop_propagation();
}))
.on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx))) .on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx)))
.on_action(cx.listener(|this, action, window, cx| { .on_action(cx.listener(|this, action, window, cx| {
this.toggle_replace(action, window, cx); this.toggle_replace(action, window, cx);
@ -2362,8 +2235,6 @@ impl Render for ProjectSearchBar {
}) })
.on_action(cx.listener(Self::select_next_match)) .on_action(cx.listener(Self::select_next_match))
.on_action(cx.listener(Self::select_prev_match)) .on_action(cx.listener(Self::select_prev_match))
.gap_2()
.w_full()
.child(search_line) .child(search_line)
.children(query_error_line) .children(query_error_line)
.children(replace_line) .children(replace_line)

View file

@ -1,8 +1,14 @@
use gpui::{Action, FocusHandle, IntoElement}; use editor::{Editor, EditorElement, EditorStyle};
use gpui::{Action, Entity, FocusHandle, Hsla, IntoElement, TextStyle};
use settings::Settings;
use theme::ThemeSettings;
use ui::{IconButton, IconButtonShape}; use ui::{IconButton, IconButtonShape};
use ui::{Tooltip, prelude::*}; use ui::{Tooltip, prelude::*};
pub(super) fn render_nav_button( use crate::ToggleReplace;
pub(super) fn render_action_button(
id_prefix: &'static str,
icon: ui::IconName, icon: ui::IconName,
active: bool, active: bool,
tooltip: &'static str, tooltip: &'static str,
@ -10,7 +16,7 @@ pub(super) fn render_nav_button(
focus_handle: FocusHandle, focus_handle: FocusHandle,
) -> impl IntoElement { ) -> impl IntoElement {
IconButton::new( IconButton::new(
SharedString::from(format!("search-nav-button-{}", action.name())), SharedString::from(format!("{id_prefix}-{}", action.name())),
icon, icon,
) )
.shape(IconButtonShape::Square) .shape(IconButtonShape::Square)
@ -26,3 +32,74 @@ pub(super) fn render_nav_button(
.tooltip(move |window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, window, cx)) .tooltip(move |window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, window, cx))
.disabled(!active) .disabled(!active)
} }
pub(crate) fn input_base_styles(border_color: Hsla, map: impl FnOnce(Div) -> Div) -> Div {
h_flex()
.min_w_32()
.map(map)
.h_8()
.pl_2()
.pr_1()
.py_1()
.border_1()
.border_color(border_color)
.rounded_lg()
}
pub(crate) fn toggle_replace_button(
id: &'static str,
focus_handle: FocusHandle,
replace_enabled: bool,
on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
) -> IconButton {
IconButton::new(id, IconName::Replace)
.shape(IconButtonShape::Square)
.style(ButtonStyle::Subtle)
.when(replace_enabled, |button| button.style(ButtonStyle::Filled))
.on_click(on_click)
.toggle_state(replace_enabled)
.tooltip({
move |window, cx| {
Tooltip::for_action_in("Toggle Replace", &ToggleReplace, &focus_handle, window, cx)
}
})
}
pub(crate) fn render_text_input(
editor: &Entity<Editor>,
color_override: Option<Color>,
app: &App,
) -> impl IntoElement {
let (color, use_syntax) = if editor.read(app).read_only(app) {
(app.theme().colors().text_disabled, false)
} else {
match color_override {
Some(color_override) => (color_override.color(app), false),
None => (app.theme().colors().text, true),
}
};
let settings = ThemeSettings::get_global(app);
let text_style = TextStyle {
color,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(1.3),
..TextStyle::default()
};
let mut editor_style = EditorStyle {
background: app.theme().colors().toolbar_background,
local_player: app.theme().players().local(),
text: text_style,
..EditorStyle::default()
};
if use_syntax {
editor_style.syntax = app.theme().syntax().clone();
}
EditorElement::new(editor, editor_style)
}

View file

@ -135,6 +135,15 @@ impl ProjectIndexDebugView {
let colors = cx.theme().colors(); let colors = cx.theme().colors();
let chunk = &state.chunks[ix]; let chunk = &state.chunks[ix];
let chunk_count = state.chunks.len();
let prev_button = Button::new(("prev", ix), "prev", cx)
.disabled(ix == 0)
.on_click(cx.listener(move |this, _, _, _| this.scroll_to_chunk(ix.saturating_sub(1))));
let next_button = Button::new(("next", ix), "next", cx)
.disabled(ix + 1 == chunk_count)
.on_click(cx.listener(move |this, _, _, _| this.scroll_to_chunk(ix + 1)));
div() div()
.text_ui(cx) .text_ui(cx)
@ -146,26 +155,10 @@ impl ProjectIndexDebugView {
.child(format!( .child(format!(
"chunk {} of {}. length: {}", "chunk {} of {}. length: {}",
ix + 1, ix + 1,
state.chunks.len(), chunk_count,
chunk.len(), chunk.len(),
)) ))
.child( .child(h_flex().child(prev_button).child(next_button)),
h_flex()
.child(
Button::new(("prev", ix), "prev")
.disabled(ix == 0)
.on_click(cx.listener(move |this, _, _, _| {
this.scroll_to_chunk(ix.saturating_sub(1))
})),
)
.child(
Button::new(("next", ix), "next")
.disabled(ix + 1 == state.chunks.len())
.on_click(cx.listener(move |this, _, _, _| {
this.scroll_to_chunk(ix + 1)
})),
),
),
) )
.child( .child(
div() div()

View file

@ -2469,7 +2469,7 @@ impl Render for KeybindingEditorModal {
.color(Color::Muted), .color(Color::Muted),
) )
.child( .child(
Button::new("show_matching", "View") Button::new("show_matching", "View", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_color(Color::Muted) .icon_color(Color::Muted)
@ -2508,10 +2508,10 @@ impl Render for KeybindingEditorModal {
h_flex() h_flex()
.gap_1() .gap_1()
.child( .child(
Button::new("cancel", "Cancel") Button::new("cancel", "Cancel", cx)
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))), .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
) )
.child(Button::new("save-btn", "Save").on_click(cx.listener( .child(Button::new("save-btn", "Save", cx).on_click(cx.listener(
|this, _event, _window, cx| { |this, _event, _window, cx| {
this.save_or_display_error(cx); this.save_or_display_error(cx);
}, },

View file

@ -1410,7 +1410,7 @@ impl Component for Table<3> {
"Project A".into_any_element(), "Project A".into_any_element(),
"High".into_any_element(), "High".into_any_element(),
"2023-12-31".into_any_element(), "2023-12-31".into_any_element(),
Button::new("view_a", "View") Button::new("view_a", "View", _cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.full_width() .full_width()
.into_any_element(), .into_any_element(),
@ -1420,7 +1420,7 @@ impl Component for Table<3> {
"Project B".into_any_element(), "Project B".into_any_element(),
"Medium".into_any_element(), "Medium".into_any_element(),
"2024-03-15".into_any_element(), "2024-03-15".into_any_element(),
Button::new("view_b", "View") Button::new("view_b", "View", _cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.full_width() .full_width()
.into_any_element(), .into_any_element(),
@ -1430,7 +1430,7 @@ impl Component for Table<3> {
"Project C".into_any_element(), "Project C".into_any_element(),
"Low".into_any_element(), "Low".into_any_element(),
"2024-06-30".into_any_element(), "2024-06-30".into_any_element(),
Button::new("view_c", "View") Button::new("view_c", "View", _cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.full_width() .full_width()
.into_any_element(), .into_any_element(),

View file

@ -668,7 +668,7 @@ impl PickerDelegate for TasksModalDelegate {
.map(|(label, action)| { .map(|(label, action)| {
let keybind = KeyBinding::for_action(&*action, window, cx); let keybind = KeyBinding::for_action(&*action, window, cx);
Button::new("edit-current-task", label) Button::new("edit-current-task", label, cx)
.when_some(keybind, |this, keybind| this.key_binding(keybind)) .when_some(keybind, |this, keybind| this.key_binding(keybind))
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx); window.dispatch_action(action.boxed_clone(), cx);
@ -691,7 +691,7 @@ impl PickerDelegate for TasksModalDelegate {
"Spawn Oneshot" "Spawn Oneshot"
}; };
Button::new("spawn-onehshot", spawn_oneshot_label) Button::new("spawn-onehshot", spawn_oneshot_label, cx)
.key_binding(keybind) .key_binding(keybind)
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {
window.dispatch_action(action.boxed_clone(), cx) window.dispatch_action(action.boxed_clone(), cx)
@ -706,14 +706,14 @@ impl PickerDelegate for TasksModalDelegate {
} else { } else {
"Spawn Without History" "Spawn Without History"
}; };
Button::new("spawn", label).key_binding(keybind).on_click( Button::new("spawn", label, cx)
move |_, window, cx| { .key_binding(keybind)
.on_click(move |_, window, cx| {
window.dispatch_action( window.dispatch_action(
menu::SecondaryConfirm.boxed_clone(), menu::SecondaryConfirm.boxed_clone(),
cx, cx,
) )
}, })
)
}, },
), ),
) )
@ -723,7 +723,7 @@ impl PickerDelegate for TasksModalDelegate {
let run_entry_label = let run_entry_label =
if is_recent_selected { "Rerun" } else { "Spawn" }; if is_recent_selected { "Rerun" } else { "Spawn" };
Button::new("spawn", run_entry_label) Button::new("spawn", run_entry_label, cx)
.key_binding(keybind) .key_binding(keybind)
.on_click(|_, window, cx| { .on_click(|_, window, cx| {
window.dispatch_action(menu::Confirm.boxed_clone(), cx); window.dispatch_action(menu::Confirm.boxed_clone(), cx);

View file

@ -315,7 +315,7 @@ impl PickerDelegate for IconThemeSelectorDelegate {
.border_t_1() .border_t_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.child( .child(
Button::new("docs", "View Icon Theme Docs") Button::new("docs", "View Icon Theme Docs", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_position(IconPosition::End) .icon_position(IconPosition::End)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -325,7 +325,7 @@ impl PickerDelegate for IconThemeSelectorDelegate {
}), }),
) )
.child( .child(
Button::new("more-icon-themes", "Install Icon Themes").on_click( Button::new("more-icon-themes", "Install Icon Themes", cx).on_click(
move |_event, window, cx| { move |_event, window, cx| {
window.dispatch_action( window.dispatch_action(
Box::new(Extensions { Box::new(Extensions {

View file

@ -373,7 +373,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
.border_t_1() .border_t_1()
.border_color(cx.theme().colors().border_variant) .border_color(cx.theme().colors().border_variant)
.child( .child(
Button::new("docs", "View Theme Docs") Button::new("docs", "View Theme Docs", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_position(IconPosition::End) .icon_position(IconPosition::End)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -383,7 +383,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
})), })),
) )
.child( .child(
Button::new("more-themes", "Install Themes").on_click(cx.listener({ Button::new("more-themes", "Install Themes", cx).on_click(cx.listener({
move |_, _, window, cx| { move |_, _, window, cx| {
window.dispatch_action( window.dispatch_action(
Box::new(Extensions { Box::new(Extensions {

View file

@ -163,7 +163,7 @@ impl ApplicationMenu {
) )
} }
fn render_standard_menu(&self, entry: &MenuEntry) -> impl IntoElement { fn render_standard_menu(&self, entry: &MenuEntry, cx: &App) -> impl IntoElement {
let current_handle = entry.handle.clone(); let current_handle = entry.handle.clone();
let menu_name = entry.menu.name.clone(); let menu_name = entry.menu.name.clone();
@ -187,6 +187,7 @@ impl ApplicationMenu {
Button::new( Button::new(
SharedString::from(format!("{}-menu-trigger", menu_name)), SharedString::from(format!("{}-menu-trigger", menu_name)),
menu_name.clone(), menu_name.clone(),
cx,
) )
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.label_size(LabelSize::Small), .label_size(LabelSize::Small),
@ -310,7 +311,7 @@ impl Render for ApplicationMenu {
this.children( this.children(
self.entries self.entries
.iter() .iter()
.map(|entry| self.render_standard_menu(entry)), .map(|entry| self.render_standard_menu(entry, cx)),
) )
}) })
} }

View file

@ -366,6 +366,7 @@ impl TitleBar {
Button::new( Button::new(
"toggle_sharing", "toggle_sharing",
if is_shared { "Unshare" } else { "Share" }, if is_shared { "Unshare" } else { "Share" },
cx,
) )
.tooltip(Tooltip::text(if is_shared { .tooltip(Tooltip::text(if is_shared {
"Stop sharing project with call participants" "Stop sharing project with call participants"

View file

@ -390,7 +390,7 @@ impl TitleBar {
if self.project.read(cx).is_disconnected(cx) { if self.project.read(cx).is_disconnected(cx) {
return Some( return Some(
Button::new("disconnected", "Disconnected") Button::new("disconnected", "Disconnected", cx)
.disabled(true) .disabled(true)
.color(Color::Disabled) .color(Color::Disabled)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
@ -407,7 +407,7 @@ impl TitleBar {
.participant_indices() .participant_indices()
.get(&host_user.id)?; .get(&host_user.id)?;
Some( Some(
Button::new("project_owner_trigger", host_user.github_login.clone()) Button::new("project_owner_trigger", host_user.github_login.clone(), cx)
.color(Color::Player(participant_index.0)) .color(Color::Player(participant_index.0))
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
@ -445,7 +445,7 @@ impl TitleBar {
"Open recent project".to_string() "Open recent project".to_string()
}; };
Button::new("project_name_trigger", name) Button::new("project_name_trigger", name, cx)
.when(!is_project_selected, |b| b.color(Color::Muted)) .when(!is_project_selected, |b| b.color(Color::Muted))
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
@ -491,7 +491,7 @@ impl TitleBar {
}?; }?;
Some( Some(
Button::new("project_branch_trigger", branch_name) Button::new("project_branch_trigger", branch_name, cx)
.color(Color::Muted) .color(Color::Muted)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
@ -590,7 +590,7 @@ impl TitleBar {
}; };
Some( Some(
Button::new("connection-status", label) Button::new("connection-status", label, cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(|_, window, cx| { .on_click(|_, window, cx| {
if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) { if let Some(auto_updater) = auto_update::AutoUpdater::get(cx) {
@ -608,9 +608,9 @@ impl TitleBar {
} }
} }
pub fn render_sign_in_button(&mut self, _: &mut Context<Self>) -> Button { pub fn render_sign_in_button(&mut self, cx: &mut Context<Self>) -> Button {
let client = self.client.clone(); let client = self.client.clone();
Button::new("sign_in", "Sign in") Button::new("sign_in", "Sign in", cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {
let client = client.clone(); let client = client.clone();

View file

@ -232,7 +232,7 @@ impl Render for ActiveToolchain {
div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| { div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| {
let term = self.term.clone(); let term = self.term.clone();
el.child( el.child(
Button::new("change-toolchain", active_toolchain.name.clone()) Button::new("change-toolchain", active_toolchain.name.clone(), cx)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, window, cx| { .on_click(cx.listener(|this, _, window, cx| {
if let Some(workspace) = this.workspace.upgrade() { if let Some(workspace) = this.workspace.upgrade() {

View file

@ -134,7 +134,7 @@ impl Component for Banner {
ComponentScope::DataDisplay ComponentScope::DataDisplay
} }
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> { fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
let severity_examples = vec![ let severity_examples = vec![
single_example( single_example(
"Default", "Default",
@ -148,7 +148,7 @@ impl Component for Banner {
.severity(Severity::Info) .severity(Severity::Info)
.child(Label::new("This is an informational message")) .child(Label::new("This is an informational message"))
.action_slot( .action_slot(
Button::new("learn-more", "Learn More") Button::new("learn-more", "Learn More", cx)
.icon(IconName::ArrowUpRight) .icon(IconName::ArrowUpRight)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.icon_position(IconPosition::End), .icon_position(IconPosition::End),
@ -160,7 +160,7 @@ impl Component for Banner {
Banner::new() Banner::new()
.severity(Severity::Success) .severity(Severity::Success)
.child(Label::new("Operation completed successfully")) .child(Label::new("Operation completed successfully"))
.action_slot(Button::new("dismiss", "Dismiss")) .action_slot(Button::new("dismiss", "Dismiss", cx))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
@ -168,7 +168,7 @@ impl Component for Banner {
Banner::new() Banner::new()
.severity(Severity::Warning) .severity(Severity::Warning)
.child(Label::new("Your settings file uses deprecated settings")) .child(Label::new("Your settings file uses deprecated settings"))
.action_slot(Button::new("update", "Update Settings")) .action_slot(Button::new("update", "Update Settings", cx))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
@ -176,7 +176,7 @@ impl Component for Banner {
Banner::new() Banner::new()
.severity(Severity::Error) .severity(Severity::Error)
.child(Label::new("Connection error: unable to connect to server")) .child(Label::new("Connection error: unable to connect to server"))
.action_slot(Button::new("reconnect", "Retry")) .action_slot(Button::new("reconnect", "Retry", cx))
.into_any_element(), .into_any_element(),
), ),
]; ];

View file

@ -1,5 +1,5 @@
use crate::component_prelude::*; use crate::component_prelude::*;
use gpui::{AnyElement, AnyView, DefiniteLength}; use gpui::{AnyElement, AnyView, DefiniteLength, FocusHandle, Focusable};
use ui_macros::RegisterComponent; use ui_macros::RegisterComponent;
use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label}; use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label};
@ -79,6 +79,7 @@ use super::button_icon::ButtonIcon;
/// ///
#[derive(IntoElement, Documented, RegisterComponent)] #[derive(IntoElement, Documented, RegisterComponent)]
pub struct Button { pub struct Button {
focus_handle: FocusHandle,
base: ButtonLike, base: ButtonLike,
label: SharedString, label: SharedString,
label_color: Option<Color>, label_color: Option<Color>,
@ -104,8 +105,9 @@ impl Button {
/// the button with the provided identifier and label text, setting all other /// the button with the provided identifier and label text, setting all other
/// properties to their default values, which can be customized using the /// properties to their default values, which can be customized using the
/// builder pattern methods provided by this struct. /// builder pattern methods provided by this struct.
pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>) -> Self { pub fn new(id: impl Into<ElementId>, label: impl Into<SharedString>, app: &App) -> Self {
Self { Self {
focus_handle: app.focus_handle(),
base: ButtonLike::new(id), base: ButtonLike::new(id),
label: label.into(), label: label.into(),
label_color: None, label_color: None,
@ -216,6 +218,12 @@ impl Button {
} }
} }
impl Focusable for Button {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Toggleable for Button { impl Toggleable for Button {
/// Sets the selected state of the button. /// Sets the selected state of the button.
/// ///
@ -483,7 +491,7 @@ impl Component for Button {
Some("A button triggers an event or action.") Some("A button triggers an event or action.")
} }
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> { fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
Some( Some(
v_flex() v_flex()
.gap_6() .gap_6()
@ -493,29 +501,29 @@ impl Component for Button {
vec![ vec![
single_example( single_example(
"Default", "Default",
Button::new("default", "Default").into_any_element(), Button::new("default", "Default", cx).into_any_element(),
), ),
single_example( single_example(
"Filled", "Filled",
Button::new("filled", "Filled") Button::new("filled", "Filled", cx)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Subtle", "Subtle",
Button::new("outline", "Subtle") Button::new("outlined", "Outlined", cx)
.style(ButtonStyle::Subtle) .style(ButtonStyle::Subtle)
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Tinted", "Tinted",
Button::new("tinted_accent_style", "Accent") Button::new("tinted_accent_style", "Accent", cx)
.style(ButtonStyle::Tinted(TintColor::Accent)) .style(ButtonStyle::Tinted(TintColor::Accent))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Transparent", "Transparent",
Button::new("transparent", "Transparent") Button::new("transparent", "Transparent", cx)
.style(ButtonStyle::Transparent) .style(ButtonStyle::Transparent)
.into_any_element(), .into_any_element(),
), ),
@ -526,25 +534,25 @@ impl Component for Button {
vec![ vec![
single_example( single_example(
"Accent", "Accent",
Button::new("tinted_accent", "Accent") Button::new("color_accent", "Accent", cx)
.style(ButtonStyle::Tinted(TintColor::Accent)) .style(ButtonStyle::Tinted(TintColor::Accent))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Error", "Error",
Button::new("tinted_negative", "Error") Button::new("tinted_negative", "Error", cx)
.style(ButtonStyle::Tinted(TintColor::Error)) .style(ButtonStyle::Tinted(TintColor::Error))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Warning", "Warning",
Button::new("tinted_warning", "Warning") Button::new("tinted_warning", "Warning", cx)
.style(ButtonStyle::Tinted(TintColor::Warning)) .style(ButtonStyle::Tinted(TintColor::Warning))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Success", "Success",
Button::new("tinted_positive", "Success") Button::new("tinted_positive", "Success", cx)
.style(ButtonStyle::Tinted(TintColor::Success)) .style(ButtonStyle::Tinted(TintColor::Success))
.into_any_element(), .into_any_element(),
), ),
@ -555,17 +563,17 @@ impl Component for Button {
vec![ vec![
single_example( single_example(
"Default", "Default",
Button::new("default_state", "Default").into_any_element(), Button::new("default_state", "Default", cx).into_any_element(),
), ),
single_example( single_example(
"Disabled", "Disabled",
Button::new("disabled", "Disabled") Button::new("disabled", "Disabled", cx)
.disabled(true) .disabled(true)
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Selected", "Selected",
Button::new("selected", "Selected") Button::new("selected", "Selected", cx)
.toggle_state(true) .toggle_state(true)
.into_any_element(), .into_any_element(),
), ),
@ -576,21 +584,21 @@ impl Component for Button {
vec![ vec![
single_example( single_example(
"Icon Start", "Icon Start",
Button::new("icon_start", "Icon Start") Button::new("icon-start", "Icon Start", cx)
.icon(IconName::Check) .icon(IconName::Check)
.icon_position(IconPosition::Start) .icon_position(IconPosition::Start)
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Icon End", "Icon End",
Button::new("icon_end", "Icon End") Button::new("icon-end", "Icon End", cx)
.icon(IconName::Check) .icon(IconName::Check)
.icon_position(IconPosition::End) .icon_position(IconPosition::End)
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
"Icon Color", "Icon Color",
Button::new("icon_color", "Icon Color") Button::new("icon_color", "Icon Color", cx)
.icon(IconName::Check) .icon(IconName::Check)
.icon_color(Color::Accent) .icon_color(Color::Accent)
.into_any_element(), .into_any_element(),

View file

@ -167,7 +167,7 @@ impl Component for Callout {
) )
} }
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> { fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
let callout_examples = vec![ let callout_examples = vec![
single_example( single_example(
"Simple with Title Only", "Simple with Title Only",
@ -178,7 +178,9 @@ impl Component for Callout {
.size(IconSize::Small), .size(IconSize::Small),
) )
.title("System maintenance scheduled for tonight") .title("System maintenance scheduled for tonight")
.primary_action(Button::new("got-it", "Got it").label_size(LabelSize::Small)) .primary_action(
Button::new("got-it", "Got it", cx).label_size(LabelSize::Small),
)
.into_any_element(), .into_any_element(),
) )
.width(px(580.)), .width(px(580.)),
@ -195,10 +197,10 @@ impl Component for Callout {
"We'll backup your current settings and update them to the new format.", "We'll backup your current settings and update them to the new format.",
) )
.primary_action( .primary_action(
Button::new("update", "Backup & Update").label_size(LabelSize::Small), Button::new("update", "Backup & Update", cx).label_size(LabelSize::Small),
) )
.secondary_action( .secondary_action(
Button::new("dismiss", "Dismiss").label_size(LabelSize::Small), Button::new("dismiss", "Dismiss", cx).label_size(LabelSize::Small),
) )
.into_any_element(), .into_any_element(),
) )
@ -214,10 +216,12 @@ impl Component for Callout {
.title("Thread reached the token limit") .title("Thread reached the token limit")
.description("Start a new thread from a summary to continue the conversation.") .description("Start a new thread from a summary to continue the conversation.")
.primary_action( .primary_action(
Button::new("new-thread", "Start New Thread").label_size(LabelSize::Small), Button::new("new-thread", "Start New Thread", cx)
.label_size(LabelSize::Small),
) )
.secondary_action( .secondary_action(
Button::new("view-summary", "View Summary").label_size(LabelSize::Small), Button::new("view-summary", "View Summary", cx)
.label_size(LabelSize::Small),
) )
.into_any_element(), .into_any_element(),
) )
@ -233,10 +237,10 @@ impl Component for Callout {
.title("Upgrade to Pro") .title("Upgrade to Pro")
.description("• Unlimited threads\n• Priority support\n• Advanced analytics") .description("• Unlimited threads\n• Priority support\n• Advanced analytics")
.primary_action( .primary_action(
Button::new("upgrade", "Upgrade Now").label_size(LabelSize::Small), Button::new("upgrade", "Upgrade Now", cx).label_size(LabelSize::Small),
) )
.secondary_action( .secondary_action(
Button::new("learn-more", "Learn More").label_size(LabelSize::Small), Button::new("learn-more", "Learn More", cx).label_size(LabelSize::Small),
) )
.into_any_element(), .into_any_element(),
) )

View file

@ -811,7 +811,7 @@ impl ContextMenu {
ListSubHeader::new(header.clone()) ListSubHeader::new(header.clone())
.inset(true) .inset(true)
.end_slot( .end_slot(
Button::new(link_id, label.clone()) Button::new(link_id, label.clone(), cx)
.color(Color::Muted) .color(Color::Muted)
.label_size(LabelSize::Small) .label_size(LabelSize::Small)
.size(ButtonSize::None) .size(ButtonSize::None)

View file

@ -59,12 +59,17 @@ impl RenderOnce for AlertModal {
.items_center() .items_center()
.gap_1() .gap_1()
.child( .child(
Button::new(self.dismiss_label.clone(), self.dismiss_label.clone()) Button::new(
.color(Color::Muted), self.dismiss_label.clone(),
self.dismiss_label.clone(),
cx,
)
.color(Color::Muted),
) )
.child(Button::new( .child(Button::new(
self.primary_action.clone(), self.primary_action.clone(),
self.primary_action.clone(), self.primary_action.clone(),
cx,
)), )),
), ),
) )

View file

@ -49,7 +49,7 @@ impl Component for SettingsContainer {
Some("A container for organizing and displaying settings in a structured manner.") Some("A container for organizing and displaying settings in a structured manner.")
} }
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> { fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
Some( Some(
v_flex() v_flex()
.gap_6() .gap_6()
@ -78,7 +78,7 @@ impl Component for SettingsContainer {
SettingsContainer::new() SettingsContainer::new()
.child(Label::new("Text Setting")) .child(Label::new("Text Setting"))
.child(Checkbox::new("checkbox", ToggleState::Unselected)) .child(Checkbox::new("checkbox", ToggleState::Unselected))
.child(Button::new("button", "Click me")) .child(Button::new("button", "Click me", cx))
.into_any_element(), .into_any_element(),
)], )],
), ),

View file

@ -165,7 +165,7 @@ impl Component for TabBar {
Some("A horizontal bar containing tabs for navigation between different views or sections.") Some("A horizontal bar containing tabs for navigation between different views or sections.")
} }
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> { fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
Some( Some(
v_flex() v_flex()
.gap_6() .gap_6()
@ -192,11 +192,11 @@ impl Component for TabBar {
vec![single_example( vec![single_example(
"Full TabBar", "Full TabBar",
TabBar::new("full_tab_bar") TabBar::new("full_tab_bar")
.start_child(Button::new("start_button", "Start")) .start_child(Button::new("start_button", "Start", cx))
.child(Tab::new("tab1")) .child(Tab::new("tab1"))
.child(Tab::new("tab2")) .child(Tab::new("tab2"))
.child(Tab::new("tab3")) .child(Tab::new("tab3"))
.end_child(Button::new("end_button", "End")) .end_child(Button::new("end_button", "End", cx))
.into_any_element(), .into_any_element(),
)], )],
), ),

Some files were not shown because too many files have changed in this diff Show more