From e354159f7770ca7f64423fbc9d3fbe674b53a3b2 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 10:38:36 +0200 Subject: [PATCH 01/10] Fix replace button in project-search not rendering as filled when active --- crates/search/src/buffer_search.rs | 48 +++++------------------- crates/search/src/project_search.rs | 57 +++++++++-------------------- crates/search/src/search_bar.rs | 36 +++++++++++++++++- 3 files changed, 63 insertions(+), 78 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 14703be7a2..60e88e9d27 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -3,7 +3,8 @@ mod registrar; use crate::{ FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, - ToggleReplace, ToggleSelection, ToggleWholeWord, search_bar::render_nav_button, + ToggleReplace, ToggleSelection, ToggleWholeWord, + search_bar::{input_base_styles, render_nav_button, toggle_replace_button}, }; use any_vec::AnyVec; use anyhow::Context as _; @@ -238,18 +239,8 @@ impl Render for BufferSearchBar { 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 input_base_styles = + |border_color| input_base_styles(border_color, |div| div.w(input_width)); let search_line = h_flex() .gap_2() @@ -304,33 +295,14 @@ impl Render for BufferSearchBar { .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.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); - })) - .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( diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 96194cdad2..a1fa2931bb 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1,7 +1,9 @@ use crate::{ BufferSearchBar, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored, - ToggleRegex, ToggleReplace, ToggleWholeWord, buffer_search::Deploy, + ToggleRegex, ToggleReplace, ToggleWholeWord, + buffer_search::Deploy, + search_bar::{input_base_styles, toggle_replace_button}, }; use anyhow::Context as _; use collections::{HashMap, HashSet}; @@ -1965,19 +1967,10 @@ impl Render for ProjectSearchBar { } let input_base_styles = |base_style: BaseStyle, panel: InputPanel| { - h_flex() - .min_w_32() - .map(|div| match base_style { - 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() + input_base_styles(search.border_color_for(panel, cx), |div| match base_style { + BaseStyle::SingleInput => div.w(input_width), + BaseStyle::MultipleInputs => div.flex_grow(), + }) }; let query_column = input_base_styles(BaseStyle::SingleInput, InputPanel::Query) @@ -2045,31 +2038,17 @@ impl Render for ProjectSearchBar { } }), ) - .child( - IconButton::new("project-search-toggle-replace", IconName::Replace) - .shape(IconButtonShape::Square) - .on_click(cx.listener(|this, _, window, cx| { - this.toggle_replace(&ToggleReplace, window, cx); - })) - .toggle_state( - self.active_project_search - .as_ref() - .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, - ) - } - }), - ); + .child(toggle_replace_button( + "project-search-toggle-replace", + focus_handle.clone(), + self.active_project_search + .as_ref() + .map(|search| search.read(cx).replace_enabled) + .unwrap_or_default(), + cx.listener(|this, _, window, cx| { + this.toggle_replace(&ToggleReplace, window, cx); + }), + )); let limit_reached = search.entity.read(cx).limit_reached; diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 805664c794..60d98a3a9f 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -1,7 +1,9 @@ -use gpui::{Action, FocusHandle, IntoElement}; +use gpui::{Action, FocusHandle, Hsla, IntoElement}; use ui::{IconButton, IconButtonShape}; use ui::{Tooltip, prelude::*}; +use crate::ToggleReplace; + pub(super) fn render_nav_button( icon: ui::IconName, active: bool, @@ -26,3 +28,35 @@ pub(super) fn render_nav_button( .tooltip(move |window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, window, cx)) .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) + } + }) +} From bce501c6960ec86e1ac7e445c051c106424f7a40 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 11:00:01 +0200 Subject: [PATCH 02/10] Render query text red in project search if no results are found --- crates/search/src/buffer_search.rs | 75 +++-------- crates/search/src/project_search.rs | 200 ++++++++++++---------------- crates/search/src/search_bar.rs | 44 +++++- 3 files changed, 147 insertions(+), 172 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 60e88e9d27..341cbf8d2b 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -4,20 +4,20 @@ use crate::{ FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord, - search_bar::{input_base_styles, render_nav_button, toggle_replace_button}, + search_bar::{input_base_styles, render_nav_button, render_text_input, toggle_replace_button}, }; use any_vec::AnyVec; use anyhow::Context as _; use collections::HashMap; use editor::{ - DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle, + DisplayPoint, Editor, EditorSettings, actions::{Backtab, Tab}, }; use futures::channel::oneshot; use gpui::{ Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, - Styled, Subscription, Task, TextStyle, Window, actions, div, + Styled, Subscription, Task, Window, actions, div, }; use language::{Language, LanguageRegistry}; use project::{ @@ -28,7 +28,6 @@ use schemars::JsonSchema; use serde::Deserialize; use settings::Settings; use std::sync::Arc; -use theme::ThemeSettings; use zed_actions::outline::ToggleOutline; use ui::{ @@ -126,46 +125,6 @@ pub struct BufferSearchBar { } impl BufferSearchBar { - fn render_text_input( - &self, - editor: &Entity, - color_override: Option, - cx: &mut Context, - ) -> 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 { self.query_editor_focused } @@ -251,13 +210,13 @@ impl Render for BufferSearchBar { 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)) + .child(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( + .when(supported_options.case, |div| { + div.child(self.render_search_option_button( SearchOptions::CASE_SENSITIVE, focus_handle.clone(), cx.listener(|this, _, window, cx| { @@ -267,26 +226,26 @@ impl Render for BufferSearchBar { cx, ) }), - ) - })) - .children(supported_options.word.then(|| { - self.render_search_option_button( + )) + }) + .when(supported_options.word, |div| { + div.child(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( + )) + }) + .when(supported_options.regex, |div| { + div.child(self.render_search_option_button( SearchOptions::REGEX, focus_handle.clone(), cx.listener(|this, _, window, cx| { this.toggle_regex(&ToggleRegex, window, cx) }), - ) - })), + )) + }), ) }), ) @@ -404,7 +363,7 @@ impl Render for BufferSearchBar { h_flex() .gap_2() .child( - input_base_styles(replacement_border).child(self.render_text_input( + input_base_styles(replacement_border).child(render_text_input( &self.replacement_editor, None, cx, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index a1fa2931bb..7e036358ed 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -3,20 +3,20 @@ use crate::{ SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord, buffer_search::Deploy, - search_bar::{input_base_styles, toggle_replace_button}, + search_bar::{input_base_styles, render_text_input, toggle_replace_button}, }; use anyhow::Context as _; use collections::{HashMap, HashSet}; use editor::{ - Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MAX_TAB_TITLE_LEN, - MultiBuffer, SelectionEffects, actions::SelectAll, items::active_match_index, + Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, SelectionEffects, + actions::SelectAll, items::active_match_index, }; use futures::{StreamExt, stream::FuturesOrdered}; use gpui::{ Action, AnyElement, AnyView, App, Axis, Context, Entity, EntityId, EventEmitter, FocusHandle, Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyContext, ParentElement, Point, - Render, SharedString, Styled, Subscription, Task, TextStyle, UpdateGlobal, WeakEntity, Window, - actions, div, + Render, SharedString, Styled, Subscription, Task, UpdateGlobal, WeakEntity, Window, actions, + div, }; use language::{Buffer, Language}; use menu::Confirm; @@ -34,7 +34,6 @@ use std::{ pin::pin, sync::Arc, }; -use theme::ThemeSettings; use ui::{ Icon, IconButton, IconButtonShape, IconName, KeyBinding, Label, LabelCommon, LabelSize, Toggleable, Tooltip, h_flex, prelude::*, utils::SearchInputWidth, v_flex, @@ -1917,37 +1916,6 @@ impl ProjectSearchBar { }) } } - - fn render_text_input(&self, editor: &Entity, cx: &Context) -> 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 { @@ -1973,6 +1941,35 @@ impl Render for ProjectSearchBar { }) }; + 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) .on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx))) .on_action(cx.listener(|this, action, window, cx| { @@ -1981,7 +1978,7 @@ impl Render for ProjectSearchBar { .on_action( 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( h_flex() .gap_1() @@ -2050,26 +2047,6 @@ impl Render for ProjectSearchBar { }), )); - let limit_reached = search.entity.read(cx).limit_reached; - - 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() .pl_2() .ml_2() @@ -2149,62 +2126,59 @@ impl Render for ProjectSearchBar { let replace_line = search.replace_enabled.then(|| { 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 replace_actions = - h_flex() - .min_w_64() - .gap_1() - .when(search.replace_enabled, |this| { - this.child( - IconButton::new("project-search-replace-next", IconName::ReplaceNext) - .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_next(&ReplaceNext, window, cx); - }) - } - })) - .tooltip({ - let focus_handle = focus_handle.clone(); - move |window, cx| { - 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, - ) - } - }), - ) - }); + let replace_actions = h_flex() + .min_w_64() + .gap_1() + .child( + IconButton::new("project-search-replace-next", IconName::ReplaceNext) + .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_next(&ReplaceNext, window, cx); + }) + } + })) + .tooltip({ + let focus_handle = focus_handle.clone(); + move |window, cx| { + 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() .w_full() @@ -2229,7 +2203,7 @@ impl Render for ProjectSearchBar { .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(render_text_input(&search.included_files_editor, None, cx)), ) .child( input_base_styles(BaseStyle::MultipleInputs, InputPanel::Exclude) @@ -2239,7 +2213,7 @@ impl Render for ProjectSearchBar { .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(render_text_input(&search.excluded_files_editor, None, cx)), ), ) .child( diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index 60d98a3a9f..ba779beeb3 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -1,4 +1,7 @@ -use gpui::{Action, FocusHandle, Hsla, 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::{Tooltip, prelude::*}; @@ -60,3 +63,42 @@ pub(crate) fn toggle_replace_button( } }) } + +pub(crate) fn render_text_input( + editor: &Entity, + color_override: Option, + 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) +} From ccd5fc20bddd1235d6ad0e06900e7aff197d509c Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 11:41:00 +0200 Subject: [PATCH 03/10] Deduplicate prev/next matches button code in search bars --- crates/search/src/project_search.rs | 64 +++++++---------------------- 1 file changed, 15 insertions(+), 49 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 7e036358ed..811650e0c7 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -3,7 +3,7 @@ use crate::{ SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord, buffer_search::Deploy, - search_bar::{input_base_styles, render_text_input, toggle_replace_button}, + search_bar::{input_base_styles, render_nav_button, render_text_input, toggle_replace_button}, }; use anyhow::Context as _; use collections::{HashMap, HashSet}; @@ -2052,54 +2052,20 @@ impl Render for ProjectSearchBar { .ml_2() .border_l_1() .border_color(cx.theme().colors().border_variant) - .child( - IconButton::new("project-search-prev-match", IconName::ChevronLeft) - .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::Prev, window, cx); - }) - } - })) - .tooltip({ - let focus_handle = focus_handle.clone(); - move |window, cx| { - Tooltip::for_action_in( - "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(render_nav_button( + IconName::ChevronLeft, + search.active_match_index.is_some(), + "Select Previous Match", + &SelectPreviousMatch, + focus_handle.clone(), + )) + .child(render_nav_button( + IconName::ChevronRight, + search.active_match_index.is_some(), + "Select Next Match", + &SelectNextMatch, + focus_handle.clone(), + )) .child( div() .id("matches") From 036bc75799d5236ae412184b99ce37367db67265 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 11:47:33 +0200 Subject: [PATCH 04/10] Deduplicate replace button code in search bars --- crates/search/src/buffer_search.rs | 82 ++++++++++------------------- crates/search/src/project_search.rs | 60 +++++---------------- 2 files changed, 42 insertions(+), 100 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 341cbf8d2b..ad41e970cb 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -359,60 +359,34 @@ impl Render for BufferSearchBar { }), ); - let replace_line = should_show_replace_input.then(|| { - h_flex() - .gap_2() - .child( - input_base_styles(replacement_border).child(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 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_nav_button( + IconName::ReplaceNext, + true, + "Replace Next Match", + &ReplaceNext, + focus_handle.clone(), + )) + .child(render_nav_button( + IconName::ReplaceAll, + true, + "Replace All Matches", + &ReplaceAll, + focus_handle, + )); + h_flex() + .gap_2() + .child(replace_column) + .child(replace_actions) + }); let query_error_line = self.query_error.as_ref().map(|error| { Label::new(error) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 811650e0c7..b90c229dbb 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -2099,52 +2099,20 @@ impl Render for ProjectSearchBar { let replace_actions = h_flex() .min_w_64() .gap_1() - .child( - IconButton::new("project-search-replace-next", IconName::ReplaceNext) - .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_next(&ReplaceNext, window, cx); - }) - } - })) - .tooltip({ - let focus_handle = focus_handle.clone(); - move |window, cx| { - 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, - ) - } - }), - ); + .child(render_nav_button( + IconName::ReplaceNext, + true, + "Replace Next Match", + &ReplaceNext, + focus_handle.clone(), + )) + .child(render_nav_button( + IconName::ReplaceAll, + true, + "Replace All Matches", + &ReplaceAll, + focus_handle, + )); h_flex() .w_full() From 88daa45537c2735fc9c03f34a86abbebdc6402b1 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 11:56:49 +0200 Subject: [PATCH 05/10] Rearrange for clarity --- crates/search/src/buffer_search.rs | 299 ++++++++++++++--------------- 1 file changed, 147 insertions(+), 152 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index ad41e970cb..f9c3c1eb30 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -201,163 +201,158 @@ impl Render for BufferSearchBar { 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(supported_options.case, |div| { + div.child(self.render_search_option_button( + SearchOptions::CASE_SENSITIVE, + focus_handle.clone(), + cx.listener(|this, _, window, cx| { + this.toggle_case_sensitive(&ToggleCaseSensitive, window, cx) + }), + )) + }) + .when(supported_options.word, |div| { + div.child(self.render_search_option_button( + SearchOptions::WHOLE_WORD, + focus_handle.clone(), + cx.listener(|this, _, window, cx| { + this.toggle_whole_word(&ToggleWholeWord, window, cx) + }), + )) + }) + .when(supported_options.regex, |div| { + div.child(self.render_search_option_button( + 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(supported_options.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(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| { + let matches_column = 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(), + )); + 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(matches_column) + .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 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(render_text_input(&self.query_editor, color_override, cx)) - .when(!hide_inline_icons, |div| { - div.child( - h_flex() - .gap_1() - .when(supported_options.case, |div| { - div.child(self.render_search_option_button( - SearchOptions::CASE_SENSITIVE, - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_case_sensitive( - &ToggleCaseSensitive, - window, - cx, - ) - }), - )) - }) - .when(supported_options.word, |div| { - div.child(self.render_search_option_button( - SearchOptions::WHOLE_WORD, - focus_handle.clone(), - cx.listener(|this, _, window, cx| { - this.toggle_whole_word(&ToggleWholeWord, window, cx) - }), - )) - }) - .when(supported_options.regex, |div| { - div.child(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(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(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) - })), - ) - }), - ); + .child(query_column) + .child(mode_column); let replace_line = should_show_replace_input.then(|| { From e2ab26ef414071398194ee5cd7f3f254eb05e4a4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 12:08:07 +0200 Subject: [PATCH 06/10] Cleanup `render_action_button` some more --- crates/search/src/buffer_search.rs | 21 ++++++++++++++------- crates/search/src/project_search.rs | 22 +++++++++++++++------- crates/search/src/search_bar.rs | 5 +++-- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index f9c3c1eb30..aace19e5ee 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -4,7 +4,9 @@ use crate::{ FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex, ToggleReplace, ToggleSelection, ToggleWholeWord, - search_bar::{input_base_styles, render_nav_button, render_text_input, toggle_replace_button}, + search_bar::{ + input_base_styles, render_action_button, render_text_input, toggle_replace_button, + }, }; use any_vec::AnyVec; use anyhow::Context as _; @@ -282,24 +284,27 @@ impl Render for BufferSearchBar { ) }) .when(!supported_options.find_in_results, |el| { + let query_focus = self.query_editor.focus_handle(cx); let matches_column = h_flex() .pl_2() .ml_1() .border_l_1() .border_color(cx.theme().colors().border_variant) - .child(render_nav_button( + .child(render_action_button( + "buffer-search-nav-button", ui::IconName::ChevronLeft, self.active_match_index.is_some(), "Select Previous Match", &SelectPreviousMatch, - focus_handle.clone(), + query_focus.clone(), )) - .child(render_nav_button( + .child(render_action_button( + "buffer-search-nav-button", ui::IconName::ChevronRight, self.active_match_index.is_some(), "Select Next Match", &SelectNextMatch, - focus_handle.clone(), + query_focus, )); el.child( IconButton::new("select-all", ui::IconName::SelectAll) @@ -363,14 +368,16 @@ impl Render for BufferSearchBar { let replace_actions = h_flex() .min_w_64() .gap_1() - .child(render_nav_button( + .child(render_action_button( + "buffer-search-replace-button", IconName::ReplaceNext, true, "Replace Next Match", &ReplaceNext, focus_handle.clone(), )) - .child(render_nav_button( + .child(render_action_button( + "buffer-search-replace-button", IconName::ReplaceAll, true, "Replace All Matches", diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index b90c229dbb..e68e4509ff 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -3,7 +3,9 @@ use crate::{ SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord, buffer_search::Deploy, - search_bar::{input_base_styles, render_nav_button, render_text_input, toggle_replace_button}, + search_bar::{ + input_base_styles, render_action_button, render_text_input, toggle_replace_button, + }, }; use anyhow::Context as _; use collections::{HashMap, HashSet}; @@ -2047,24 +2049,28 @@ impl Render for ProjectSearchBar { }), )); + let query_focus = search.query_editor.focus_handle(cx); + let matches_column = h_flex() .pl_2() .ml_2() .border_l_1() .border_color(cx.theme().colors().border_variant) - .child(render_nav_button( + .child(render_action_button( + "project-search-nav-button", IconName::ChevronLeft, search.active_match_index.is_some(), "Select Previous Match", &SelectPreviousMatch, - focus_handle.clone(), + query_focus.clone(), )) - .child(render_nav_button( + .child(render_action_button( + "project-search-nav-button", IconName::ChevronRight, search.active_match_index.is_some(), "Select Next Match", &SelectNextMatch, - focus_handle.clone(), + query_focus, )) .child( div() @@ -2099,14 +2105,16 @@ impl Render for ProjectSearchBar { let replace_actions = h_flex() .min_w_64() .gap_1() - .child(render_nav_button( + .child(render_action_button( + "project-search-replace-button", IconName::ReplaceNext, true, "Replace Next Match", &ReplaceNext, focus_handle.clone(), )) - .child(render_nav_button( + .child(render_action_button( + "project-search-replace-button", IconName::ReplaceAll, true, "Replace All Matches", diff --git a/crates/search/src/search_bar.rs b/crates/search/src/search_bar.rs index ba779beeb3..2805b0c62d 100644 --- a/crates/search/src/search_bar.rs +++ b/crates/search/src/search_bar.rs @@ -7,7 +7,8 @@ use ui::{Tooltip, prelude::*}; use crate::ToggleReplace; -pub(super) fn render_nav_button( +pub(super) fn render_action_button( + id_prefix: &'static str, icon: ui::IconName, active: bool, tooltip: &'static str, @@ -15,7 +16,7 @@ pub(super) fn render_nav_button( focus_handle: FocusHandle, ) -> impl IntoElement { IconButton::new( - SharedString::from(format!("search-nav-button-{}", action.name())), + SharedString::from(format!("{id_prefix}-{}", action.name())), icon, ) .shape(IconButtonShape::Square) From 3cda09f875343837ee51d0b41574b97aa1c5c868 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 12:32:55 +0200 Subject: [PATCH 07/10] More cleanups --- crates/search/src/buffer_search.rs | 165 +++++++++++++--------------- crates/search/src/project_search.rs | 93 +++++++--------- 2 files changed, 117 insertions(+), 141 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index aace19e5ee..302eb77e69 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -17,9 +17,9 @@ use editor::{ }; use futures::channel::oneshot; use gpui::{ - Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable, - InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, - Styled, Subscription, Task, Window, actions, div, + Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _, + IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task, + Window, actions, div, }; use language::{Language, LanguageRegistry}; use project::{ @@ -147,7 +147,14 @@ impl Render for BufferSearchBar { let hide_inline_icons = self.editor_needed_width > 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| { query_editor.placeholder_text().is_none() @@ -182,20 +189,16 @@ impl Render for BufferSearchBar { } }) .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 mut key_context = KeyContext::new_with_defaults(); - key_context.add("BufferSearchBar"); - if in_replace { - key_context.add("in_replace"); - } + let theme_colors = cx.theme().colors(); let query_border = if self.query_error.is_some() { Color::Error.color(cx) } else { - cx.theme().colors().border + theme_colors.border }; - let replacement_border = cx.theme().colors().border; + let replacement_border = theme_colors.border; let container_width = window.viewport_size().width; let input_width = SearchInputWidth::calc_width(container_width); @@ -211,27 +214,27 @@ impl Render for BufferSearchBar { div.child( h_flex() .gap_1() - .when(supported_options.case, |div| { - div.child(self.render_search_option_button( - SearchOptions::CASE_SENSITIVE, + .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(supported_options.word, |div| { - div.child(self.render_search_option_button( - SearchOptions::WHOLE_WORD, + .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(supported_options.regex, |div| { - div.child(self.render_search_option_button( - SearchOptions::REGEX, + .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) @@ -244,7 +247,7 @@ impl Render for BufferSearchBar { let mode_column = h_flex() .gap_1() .min_w_64() - .when(supported_options.replacement, |this| { + .when(replacement, |this| { this.child(toggle_replace_button( "buffer-search-bar-toggle-replace-button", focus_handle.clone(), @@ -254,7 +257,7 @@ impl Render for BufferSearchBar { }), )) }) - .when(supported_options.selection, |this| { + .when(selection, |this| { this.child( IconButton::new( "buffer-search-bar-toggle-search-selection-button", @@ -283,13 +286,13 @@ impl Render for BufferSearchBar { }), ) }) - .when(!supported_options.find_in_results, |el| { + .when(!find_in_results, |el| { let query_focus = self.query_editor.focus_handle(cx); let matches_column = h_flex() .pl_2() .ml_1() .border_l_1() - .border_color(cx.theme().colors().border_variant) + .border_color(theme_colors.border_variant) .child(render_action_button( "buffer-search-nav-button", ui::IconName::ChevronLeft, @@ -304,27 +307,17 @@ impl Render for BufferSearchBar { self.active_match_index.is_some(), "Select Next Match", &SelectNextMatch, - query_focus, + query_focus.clone(), )); - 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, - ) - } - }), - ) + + el.child(render_action_button( + "buffer-search-nav-button", + IconName::SelectAll, + true, + "Select All Matches", + &SelectAllMatches, + query_focus, + )) .child(matches_column) .when(!narrow_mode, |this| { this.child(h_flex().ml_2().min_w(rems_from_px(40.)).child( @@ -338,22 +331,20 @@ impl Render for BufferSearchBar { )) }) }) - .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) - })), - ) + .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() .gap_2() - .when(supported_options.find_in_results, |el| { + .when(find_in_results, |el| { el.child(Label::new("Find in results").color(Color::Hint)) }) .child(query_column) @@ -390,6 +381,12 @@ impl Render for BufferSearchBar { .child(replace_actions) }); + let mut key_context = KeyContext::new_with_defaults(); + key_context.add("BufferSearchBar"); + if in_replace { + key_context.add("in_replace"); + } + let query_error_line = self.query_error.as_ref().map(|error| { Label::new(error) .size(LabelSize::Small) @@ -398,6 +395,21 @@ impl Render for BufferSearchBar { .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() .id("buffer_search") .gap_2() @@ -416,43 +428,26 @@ impl Render for BufferSearchBar { 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)) .when(in_replace, |this| { this.on_action(cx.listener(Self::replace_next)) .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)) }) - .when(self.supported_options(cx).word, |this| { + .when(word, |this| { 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)) }) - .when(self.supported_options(cx).selection, |this| { + .when(selection, |this| { this.on_action(cx.listener(Self::toggle_selection)) }) - .child(h_flex().relative().child(search_line.w_full()).when( - !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() - }, - )) + .child(search_line) .children(query_error_line) .children(replace_line) } @@ -882,16 +877,6 @@ impl BufferSearchBar { self.update_matches(!updated, window, cx) } - fn render_search_option_button( - &self, - option: SearchOptions, - focus_handle: FocusHandle, - action: Action, - ) -> impl IntoElement + use { - 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) { if let Some(active_editor) = self.active_searchable_item.as_ref() { let handle = active_editor.item_focus_handle(cx); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index e68e4509ff..4e5a580bdd 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -2130,6 +2130,45 @@ impl Render for ProjectSearchBar { }); 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() .w_full() .gap_2() @@ -2137,62 +2176,14 @@ impl Render for ProjectSearchBar { h_flex() .gap_2() .w(input_width) - .child( - 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)), - ) - .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(render_text_input(&search.excluded_files_editor, None, 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(include) + .child(exclude), ) + .child(mode_column) }); let mut key_context = KeyContext::default(); - key_context.add("ProjectSearchBar"); - if search .replacement_editor .focus_handle(cx) From 9b6b7f1412af44d9c439465a16f67a57e6de317f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 12:00:27 +0200 Subject: [PATCH 08/10] Remove style differences between project and buffer search --- crates/search/src/buffer_search.rs | 29 ++++++++++++++++------------- crates/search/src/project_search.rs | 8 +++++--- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 302eb77e69..45aa498d65 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -290,7 +290,7 @@ impl Render for BufferSearchBar { let query_focus = self.query_editor.focus_handle(cx); let matches_column = h_flex() .pl_2() - .ml_1() + .ml_2() .border_l_1() .border_color(theme_colors.border_variant) .child(render_action_button( @@ -308,7 +308,18 @@ impl Render for BufferSearchBar { "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", @@ -319,17 +330,6 @@ impl Render for BufferSearchBar { query_focus, )) .child(matches_column) - .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(find_in_results, |el| { el.child(render_action_button( @@ -343,6 +343,7 @@ impl Render for BufferSearchBar { }); let search_line = h_flex() + .w_full() .gap_2() .when(find_in_results, |el| { el.child(Label::new("Find in results").color(Color::Hint)) @@ -376,6 +377,7 @@ impl Render for BufferSearchBar { focus_handle, )); h_flex() + .w_full() .gap_2() .child(replace_column) .child(replace_actions) @@ -414,6 +416,7 @@ impl Render for BufferSearchBar { .id("buffer_search") .gap_2() .py(px(1.0)) + .w_full() .track_scroll(&self.scroll_handle) .key_context(key_context) .capture_action(cx.listener(Self::tab)) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 4e5a580bdd..63478373e9 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -2009,6 +2009,7 @@ impl Render for ProjectSearchBar { let mode_column = h_flex() .gap_1() + .min_w_64() .child( IconButton::new("project-search-filter-button", IconName::Filter) .shape(IconButtonShape::Square) @@ -2075,7 +2076,8 @@ impl Render for ProjectSearchBar { .child( div() .id("matches") - .ml_1() + .ml_2() + .min_w(rems_from_px(40.)) .child(Label::new(match_text).size(LabelSize::Small).color( if search.active_match_index.is_some() { Color::Default @@ -2201,7 +2203,9 @@ impl Render for ProjectSearchBar { }); v_flex() + .gap_2() .py(px(1.0)) + .w_full() .key_context(key_context) .on_action(cx.listener(|this, _: &ToggleFocus, window, cx| { this.move_focus_to_results(window, cx) @@ -2248,8 +2252,6 @@ impl Render for ProjectSearchBar { }) .on_action(cx.listener(Self::select_next_match)) .on_action(cx.listener(Self::select_prev_match)) - .gap_2() - .w_full() .child(search_line) .children(query_error_line) .children(replace_line) From 6e540a58fad53b6249c0a14da3e0fee114fb9b66 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 13:55:14 +0200 Subject: [PATCH 09/10] Make tab cycling behave symmetrical in search buffer --- crates/search/src/buffer_search.rs | 52 +++++++++++++++-------------- crates/search/src/project_search.rs | 47 +++++++++----------------- crates/workspace/src/workspace.rs | 8 ++--- 3 files changed, 44 insertions(+), 63 deletions(-) diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 45aa498d65..ccef198f04 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -697,7 +697,7 @@ impl BufferSearchBar { active_editor.search_bar_visibility_changed(false, window, cx); active_editor.toggle_filtered_search_ranges(false, window, cx); let handle = active_editor.item_focus_handle(cx); - self.focus(&handle, window, cx); + self.focus(&handle, window); } cx.emit(Event::UpdateLocation); cx.emit(ToolbarItemEvent::ChangeLocation( @@ -853,7 +853,7 @@ impl BufferSearchBar { } pub fn focus_replace(&mut self, window: &mut Window, cx: &mut Context) { - self.focus(&self.replacement_editor.focus_handle(cx), window, cx); + self.focus(&self.replacement_editor.focus_handle(cx), window); cx.notify(); } @@ -1295,28 +1295,32 @@ impl BufferSearchBar { } fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context) { - // Search -> Replace -> Editor - 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(); + self.cycle_field(Direction::Next, window, cx); } fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context) { - // Search -> Replace -> Search - let focus_handle = if self.replace_enabled && self.query_editor_focused { - self.replacement_editor.focus_handle(cx) - } else if self.replacement_editor_focused { - self.query_editor.focus_handle(cx) - } else { - return; + self.cycle_field(Direction::Prev, window, cx); + } + fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context) { + let mut handles = vec![self.query_editor.focus_handle(cx)]; + if self.replace_enabled { + handles.push(self.replacement_editor.focus_handle(cx)); + } + 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(); } @@ -1364,10 +1368,8 @@ impl BufferSearchBar { } } - fn focus(&self, handle: &gpui::FocusHandle, window: &mut Window, cx: &mut Context) { - cx.on_next_frame(window, |_, window, _| { - window.invalidate_character_coordinates(); - }); + fn focus(&self, handle: &gpui::FocusHandle, window: &mut Window) { + window.invalidate_character_coordinates(); window.focus(handle); } @@ -1379,7 +1381,7 @@ impl BufferSearchBar { } else { self.query_editor.focus_handle(cx) }; - self.focus(&handle, window, cx); + self.focus(&handle, window); cx.notify(); } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 63478373e9..69beeec3be 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -11,7 +11,8 @@ use anyhow::Context as _; use collections::{HashMap, HashSet}; use editor::{ Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, SelectionEffects, - actions::SelectAll, items::active_match_index, + actions::{Backtab, SelectAll, Tab}, + items::active_match_index, }; use futures::{StreamExt, stream::FuturesOrdered}; use gpui::{ @@ -1613,16 +1614,11 @@ impl ProjectSearchBar { } } - fn tab(&mut self, _: &editor::actions::Tab, window: &mut Window, cx: &mut Context) { + fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context) { self.cycle_field(Direction::Next, window, cx); } - fn backtab( - &mut self, - _: &editor::actions::Backtab, - window: &mut Window, - cx: &mut Context, - ) { + fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context) { self.cycle_field(Direction::Prev, window, cx); } @@ -1637,29 +1633,22 @@ impl ProjectSearchBar { fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context) { let active_project_search = match &self.active_project_search { Some(active_project_search) => active_project_search, - - None => { - return; - } + None => return, }; 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 { - views.push(&project_view.replacement_editor); + views.push(project_view.replacement_editor.focus_handle(cx)); } if project_view.filters_enabled { views.extend([ - &project_view.included_files_editor, - &project_view.excluded_files_editor, + project_view.included_files_editor.focus_handle(cx), + project_view.excluded_files_editor.focus_handle(cx), ]); } - let current_index = match views - .iter() - .enumerate() - .find(|(_, editor)| editor.focus_handle(cx).is_focused(window)) - { - Some((index, _)) => index, + let current_index = match views.iter().position(|focus| focus.is_focused(window)) { + Some(index) => index, None => return, }; @@ -1668,8 +1657,8 @@ impl ProjectSearchBar { Direction::Prev if current_index == 0 => views.len() - 1, Direction::Prev => (current_index - 1) % views.len(), }; - let next_focus_handle = views[new_index].focus_handle(cx); - window.focus(&next_focus_handle); + let next_focus_handle = &views[new_index]; + window.focus(next_focus_handle); cx.stop_propagation(); }); } @@ -2213,14 +2202,8 @@ impl Render for ProjectSearchBar { .on_action(cx.listener(|this, _: &ToggleFilters, window, cx| { this.toggle_filters(window, cx); })) - .capture_action(cx.listener(|this, action, window, cx| { - this.tab(action, window, cx); - cx.stop_propagation(); - })) - .capture_action(cx.listener(|this, action, window, cx| { - this.backtab(action, window, cx); - cx.stop_propagation(); - })) + .capture_action(cx.listener(Self::tab)) + .capture_action(cx.listener(Self::backtab)) .on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx))) .on_action(cx.listener(|this, action, window, cx| { this.toggle_replace(action, window, cx); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 98794e54cd..699ed07b43 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3880,9 +3880,7 @@ impl Workspace { local, focus_changed, } => { - cx.on_next_frame(window, |_, window, _| { - window.invalidate_character_coordinates(); - }); + window.invalidate_character_coordinates(); pane.update(cx, |pane, _| { pane.track_alternate_file_items(); @@ -3923,9 +3921,7 @@ impl Workspace { } } pane::Event::Focus => { - cx.on_next_frame(window, |_, window, _| { - window.invalidate_character_coordinates(); - }); + window.invalidate_character_coordinates(); self.handle_pane_focused(pane.clone(), window, cx); } pane::Event::ZoomIn => { From e56672e54247a6516f00e84399aba1f06a13de1f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Wed, 13 Aug 2025 16:40:11 +0200 Subject: [PATCH 10/10] `impl Focusable for Button` --- crates/agent_ui/src/acp/thread_view.rs | 20 +++---- crates/agent_ui/src/active_thread.rs | 10 ++-- crates/agent_ui/src/agent_configuration.rs | 6 ++- .../add_llm_provider_modal.rs | 8 +-- .../configure_context_server_modal.rs | 4 +- crates/agent_ui/src/agent_diff.rs | 14 ++--- crates/agent_ui/src/agent_panel.rs | 54 ++++++++++--------- crates/agent_ui/src/inline_prompt_editor.rs | 6 +-- .../agent_ui/src/language_model_selector.rs | 5 +- crates/agent_ui/src/message_editor.rs | 12 ++--- crates/agent_ui/src/profile_selector.rs | 4 +- crates/agent_ui/src/text_thread_editor.rs | 41 +++++++------- crates/agent_ui/src/ui/agent_notification.rs | 16 +++--- crates/agent_ui/src/ui/end_trial_upsell.rs | 2 +- crates/agent_ui/src/ui/onboarding_modal.rs | 4 +- .../agent_ui/src/ui/preview/usage_callouts.rs | 2 +- .../src/agent_api_keys_onboarding.rs | 2 +- crates/ai_onboarding/src/ai_onboarding.rs | 20 +++---- crates/ai_onboarding/src/ai_upsell_card.rs | 26 +++++---- .../src/edit_prediction_onboarding_content.rs | 1 + crates/assistant_tools/src/find_path_tool.rs | 2 +- crates/assistant_tools/src/web_search_tool.rs | 2 +- crates/collab_ui/src/chat_panel.rs | 2 +- crates/collab_ui/src/collab_panel.rs | 2 +- .../src/collab_panel/channel_modal.rs | 2 +- crates/collab_ui/src/notification_panel.rs | 32 +++++------ .../incoming_call_notification.rs | 4 +- .../project_shared_notification.rs | 10 ++-- .../stories/collab_notification.rs | 8 +-- crates/copilot/src/sign_in.rs | 10 ++-- crates/debugger_tools/src/dap_log.rs | 3 +- crates/debugger_ui/src/debugger_panel.rs | 7 +-- crates/debugger_ui/src/new_process_modal.rs | 12 ++--- crates/debugger_ui/src/onboarding_modal.rs | 4 +- crates/diagnostics/src/diagnostics.rs | 8 +-- crates/diagnostics/src/items.rs | 2 +- crates/editor/src/editor.rs | 10 ++-- .../src/components/feature_upsell.rs | 2 +- crates/extensions_ui/src/extensions_ui.rs | 24 ++++++--- crates/feedback/src/feedback_modal.rs | 8 +-- crates/file_finder/src/file_finder.rs | 2 +- crates/git_ui/src/blame_ui.rs | 2 + crates/git_ui/src/commit_modal.rs | 2 +- crates/git_ui/src/commit_tooltip.rs | 2 + crates/git_ui/src/conflict_view.rs | 6 +-- crates/git_ui/src/git_panel.rs | 12 ++--- crates/git_ui/src/onboarding.rs | 4 +- crates/git_ui/src/project_diff.rs | 14 ++--- crates/go_to_line/src/cursor_position.rs | 2 +- crates/image_viewer/src/image_info.rs | 2 +- .../language_models/src/provider/anthropic.rs | 5 +- .../language_models/src/provider/bedrock.rs | 8 ++- crates/language_models/src/provider/cloud.rs | 33 +++++++----- .../src/provider/copilot_chat.rs | 4 +- .../language_models/src/provider/deepseek.rs | 4 +- crates/language_models/src/provider/google.rs | 4 +- .../language_models/src/provider/lmstudio.rs | 9 ++-- .../language_models/src/provider/mistral.rs | 5 +- crates/language_models/src/provider/ollama.rs | 10 ++-- .../language_models/src/provider/open_ai.rs | 7 ++- .../src/provider/open_ai_compatible.rs | 2 +- .../src/provider/open_router.rs | 5 +- crates/language_models/src/provider/vercel.rs | 4 +- crates/language_models/src/provider/x_ai.rs | 4 +- .../src/ui/instruction_list_item.rs | 14 +++-- .../src/active_buffer_language.rs | 2 +- crates/language_tools/src/key_context_view.rs | 6 +-- crates/language_tools/src/lsp_log.rs | 53 +++++++++--------- crates/notifications/src/status_toast.rs | 2 +- crates/onboarding/src/ai_setup_page.rs | 6 +-- crates/onboarding/src/onboarding.rs | 2 +- crates/onboarding/src/welcome.rs | 2 +- crates/panel/src/panel.rs | 8 +-- crates/project_panel/src/project_panel.rs | 2 +- .../src/disconnected_overlay.rs | 4 +- crates/recent_projects/src/recent_projects.rs | 4 +- crates/recent_projects/src/remote_servers.rs | 2 +- crates/repl/src/components/kernel_options.rs | 2 +- crates/repl/src/session.rs | 4 +- crates/rules_library/src/rules_library.rs | 26 ++++----- crates/search/src/project_search.rs | 10 ++-- .../src/project_index_debug_view.rs | 29 ++++------ crates/settings_ui/src/keybindings.rs | 6 +-- crates/settings_ui/src/ui_components/table.rs | 6 +-- crates/tasks_ui/src/modal.rs | 14 ++--- .../theme_selector/src/icon_theme_selector.rs | 4 +- crates/theme_selector/src/theme_selector.rs | 4 +- crates/title_bar/src/application_menu.rs | 5 +- crates/title_bar/src/collab.rs | 1 + crates/title_bar/src/title_bar.rs | 14 ++--- .../src/active_toolchain.rs | 2 +- crates/ui/src/components/banner.rs | 10 ++-- crates/ui/src/components/button/button.rs | 44 ++++++++------- crates/ui/src/components/callout.rs | 20 ++++--- crates/ui/src/components/context_menu.rs | 2 +- .../components/notification/alert_modal.rs | 9 +++- .../ui/src/components/settings_container.rs | 4 +- crates/ui/src/components/tab_bar.rs | 6 +-- crates/ui/src/components/tooltip.rs | 4 +- crates/ui_prompt/src/ui_prompt.rs | 2 +- crates/welcome/src/multibuffer_hint.rs | 2 +- crates/welcome/src/welcome.rs | 13 ++--- crates/workspace/src/notifications.rs | 16 +++--- crates/workspace/src/theme_preview.rs | 2 +- crates/zed/src/zed/component_preview.rs | 2 +- crates/zed/src/zed/migrate.rs | 2 +- crates/zeta/src/rate_completion_modal.rs | 6 ++- 107 files changed, 524 insertions(+), 417 deletions(-) diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index da7915222e..b8ebddbe44 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -1415,7 +1415,7 @@ impl AcpThreadView { .text_color(cx.theme().colors().text_muted) .child(self.render_markdown(markdown, default_markdown_style(false, window, cx))) .child( - Button::new(button_id, "Collapse Output") + Button::new(button_id, "Collapse Output", cx) .full_width() .style(ButtonStyle::Outlined) .label_size(LabelSize::Small) @@ -1455,7 +1455,7 @@ impl AcpThreadView { .border_color(self.tool_card_border_color(cx)) .overflow_hidden() .child( - Button::new(button_id, label) + Button::new(button_id, label, cx) .label_size(LabelSize::Small) .color(Color::Muted) .icon(IconName::ArrowUpRight) @@ -1498,7 +1498,7 @@ impl AcpThreadView { ) .child(h_flex().gap_0p5().children(options.iter().map(|option| { 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 { acp::PermissionOptionKind::AllowOnce => { this.icon(IconName::Check).icon_color(Color::Success) @@ -1628,6 +1628,7 @@ impl AcpThreadView { Button::new( SharedString::from(format!("stop-terminal-{}", terminal.entity_id())), "Stop", + cx, ) .icon(IconName::Stop) .icon_position(IconPosition::Start) @@ -1932,7 +1933,7 @@ impl AcpThreadView { { let upgrade_message = upgrade_message.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| { this.workspace .update(cx, |workspace, cx| { @@ -2264,7 +2265,7 @@ impl AcpThreadView { ) .child(Divider::vertical().color(DividerColor::Border)) .child( - Button::new("reject-all-changes", "Reject All") + Button::new("reject-all-changes", "Reject All", cx) .label_size(LabelSize::Small) .disabled(pending_edits) .when(pending_edits, |this| { @@ -2289,7 +2290,7 @@ impl AcpThreadView { }), ) .child( - Button::new("keep-all-changes", "Keep All") + Button::new("keep-all-changes", "Keep All", cx) .label_size(LabelSize::Small) .disabled(pending_edits) .when(pending_edits, |this| { @@ -2395,7 +2396,7 @@ impl AcpThreadView { .gap_1() .visible_on_hover("edited-code") .child( - Button::new("review", "Review") + Button::new("review", "Review", cx) .label_size(LabelSize::Small) .on_click({ let buffer = buffer.clone(); @@ -2406,7 +2407,7 @@ impl AcpThreadView { ) .child(Divider::vertical().color(DividerColor::BorderVariant)) .child( - Button::new("reject-file", "Reject") + Button::new("reject-file", "Reject", cx) .label_size(LabelSize::Small) .disabled(pending_edits) .on_click({ @@ -2426,7 +2427,7 @@ impl AcpThreadView { }), ) .child( - Button::new("keep-file", "Keep") + Button::new("keep-file", "Keep", cx) .label_size(LabelSize::Small) .disabled(pending_edits) .on_click({ @@ -3114,6 +3115,7 @@ impl Render for AcpThreadView { Button::new( SharedString::from(method.id.0.clone()), method.name.clone(), + cx, ) .on_click({ let method_id = method.id.clone(); diff --git a/crates/agent_ui/src/active_thread.rs b/crates/agent_ui/src/active_thread.rs index ffed62d41f..3b25610f53 100644 --- a/crates/agent_ui/src/active_thread.rs +++ b/crates/agent_ui/src/active_thread.rs @@ -2281,7 +2281,7 @@ impl ActiveThread { } let restore_checkpoint_button = - Button::new(("restore-checkpoint", ix), "Restore Checkpoint") + Button::new(("restore-checkpoint", ix), "Restore Checkpoint", cx) .icon(if error.is_some() { IconName::XCircle } else { @@ -2371,7 +2371,7 @@ impl ActiveThread { .gap_1() .justify_end() .child( - Button::new("dismiss-feedback-message", "Cancel") + Button::new("dismiss-feedback-message", "Cancel", cx) .label_size(LabelSize::Small) .key_binding( KeyBinding::for_action_in( @@ -2394,6 +2394,7 @@ impl ActiveThread { Button::new( "submit-feedback-message", "Share Feedback", + cx, ) .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .label_size(LabelSize::Small) @@ -3217,6 +3218,7 @@ impl ActiveThread { Button::new( "always-allow-tool-action", "Always Allow", + cx, ) .label_size(LabelSize::Small) .icon(IconName::CheckDouble) @@ -3254,7 +3256,7 @@ impl ActiveThread { }) .child({ let tool_id = tool_use.id.clone(); - Button::new("allow-tool-action", "Allow") + Button::new("allow-tool-action", "Allow", cx) .label_size(LabelSize::Small) .icon(IconName::Check) .icon_position(IconPosition::Start) @@ -3274,7 +3276,7 @@ impl ActiveThread { .child({ let tool_id = tool_use.id.clone(); let tool_name: Arc = tool_use.name.into(); - Button::new("deny-tool", "Deny") + Button::new("deny-tool", "Deny", cx) .label_size(LabelSize::Small) .icon(IconName::Close) .icon_position(IconPosition::Start) diff --git a/crates/agent_ui/src/agent_configuration.rs b/crates/agent_ui/src/agent_configuration.rs index 5f72fa58c8..ce719eaf7e 100644 --- a/crates/agent_ui/src/agent_configuration.rs +++ b/crates/agent_ui/src/agent_configuration.rs @@ -281,6 +281,7 @@ impl AgentConfiguration { Button::new( SharedString::from(format!("new-thread-{provider_id}")), "Start New Thread", + cx, ) .icon_position(IconPosition::Start) .icon(IconName::Plus) @@ -339,7 +340,7 @@ impl AgentConfiguration { .child( PopoverMenu::new("add-provider-popover") .trigger( - Button::new("add-provider", "Add Provider") + Button::new("add-provider", "Add Provider", cx) .icon_position(IconPosition::Start) .icon(IconName::Plus) .icon_size(IconSize::Small) @@ -552,7 +553,7 @@ impl AgentConfiguration { .gap_2() .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) .layer(ElevationIndex::ModalSurface) .full_width() @@ -569,6 +570,7 @@ impl AgentConfiguration { Button::new( "install-context-server-extensions", "Install MCP Extensions", + cx, ) .style(ButtonStyle::Filled) .layer(ElevationIndex::ModalSurface) diff --git a/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs b/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs index 401a633488..d35b47be59 100644 --- a/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs +++ b/crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs @@ -281,7 +281,7 @@ impl AddLlmProviderModal { .justify_between() .child(Label::new("Models").size(LabelSize::Small)) .child( - Button::new("add-model", "Add Model") + Button::new("add-model", "Add Model", cx) .icon(IconName::Plus) .icon_position(IconPosition::Start) .icon_size(IconSize::XSmall) @@ -324,7 +324,7 @@ impl AddLlmProviderModal { .child(model.max_tokens.clone()) .when(has_more_than_one_model, |this| { this.child( - Button::new(("remove-model", ix), "Remove Model") + Button::new(("remove-model", ix), "Remove Model", cx) .icon(IconName::Trash) .icon_position(IconPosition::Start) .icon_size(IconSize::XSmall) @@ -400,7 +400,7 @@ impl Render for AddLlmProviderModal { h_flex() .gap_1() .child( - Button::new("cancel", "Cancel") + Button::new("cancel", "Cancel", cx) .key_binding( KeyBinding::for_action_in( &menu::Cancel, @@ -415,7 +415,7 @@ impl Render for AddLlmProviderModal { })), ) .child( - Button::new("save-server", "Save Provider") + Button::new("save-server", "Save Provider", cx) .key_binding( KeyBinding::for_action_in( &menu::Confirm, diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs index 32360dd56e..1bc7ebc749 100644 --- a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs @@ -564,7 +564,7 @@ impl ConfigureContextServerModal { } = &self.source { Some( - Button::new("open-repository", "Open Repository") + Button::new("open-repository", "Open Repository", cx) .icon(IconName::ArrowUpRight) .icon_color(Color::Muted) .icon_size(IconSize::Small) @@ -600,6 +600,7 @@ impl ConfigureContextServerModal { } else { "Dismiss" }, + cx, ) .key_binding( KeyBinding::for_action_in(&menu::Cancel, &focus_handle, window, cx) @@ -617,6 +618,7 @@ impl ConfigureContextServerModal { } else { "Configure Server" }, + cx, ) .disabled(is_connecting) .key_binding( diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index 0abc5280f4..1c46efef2b 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -726,7 +726,7 @@ impl Render for AgentDiffPane { .gap_2() .child("No changes to review") .child( - Button::new("continue-iterating", "Continue Iterating") + Button::new("continue-iterating", "Continue Iterating", cx) .style(ButtonStyle::Filled) .icon(IconName::ForwardArrow) .icon_position(IconPosition::Start) @@ -806,7 +806,7 @@ fn render_diff_hunk_controls( .block_mouse_except_scroll() .shadow_md() .children(vec![ - Button::new(("reject", row as u64), "Reject") + Button::new(("reject", row as u64), "Reject", cx) .disabled(is_created_file) .key_binding( 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( KeyBinding::for_action_in(&Keep, &editor.read(cx).focus_handle(cx), window, cx) .map(|kb| kb.size(rems_from_px(12.))), @@ -1147,7 +1147,7 @@ impl Render for AgentDiffToolbar { h_flex() .gap_0p5() .child( - Button::new("reject-all", "Reject All") + Button::new("reject-all", "Reject All", cx) .key_binding({ KeyBinding::for_action_in( &RejectAll, @@ -1162,7 +1162,7 @@ impl Render for AgentDiffToolbar { })), ) .child( - Button::new("keep-all", "Keep All") + Button::new("keep-all", "Keep All", cx) .key_binding({ KeyBinding::for_action_in( &KeepAll, @@ -1242,7 +1242,7 @@ impl Render for AgentDiffToolbar { .child( h_group_sm() .child( - Button::new("reject-all", "Reject All") + Button::new("reject-all", "Reject All", cx) .key_binding({ KeyBinding::for_action_in( &RejectAll, @@ -1257,7 +1257,7 @@ impl Render for AgentDiffToolbar { })), ) .child( - Button::new("keep-all", "Keep All") + Button::new("keep-all", "Keep All", cx) .key_binding({ KeyBinding::for_action_in( &KeepAll, diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 87e4dd822c..387b9e5598 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -2444,7 +2444,7 @@ impl AgentPanel { .gap_1() .max_w_48() .child( - Button::new("context", "Add Context") + Button::new("context", "Add Context", cx) .label_size(LabelSize::Small) .icon(IconName::FileCode) .icon_position(IconPosition::Start) @@ -2465,7 +2465,7 @@ impl AgentPanel { }), ) .child( - Button::new("mode", "Switch Model") + Button::new("mode", "Switch Model", cx) .label_size(LabelSize::Small) .icon(IconName::DatabaseZap) .icon_position(IconPosition::Start) @@ -2486,7 +2486,7 @@ impl AgentPanel { }), ) .child( - Button::new("settings", "View Settings") + Button::new("settings", "View Settings", cx) .label_size(LabelSize::Small) .icon(IconName::Settings) .icon_position(IconPosition::Start) @@ -2529,7 +2529,7 @@ impl AgentPanel { self.render_empty_state_section_header( "Recent", Some( - Button::new("view-history", "View All") + Button::new("view-history", "View All", cx) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) .key_binding( @@ -2727,7 +2727,7 @@ impl AgentPanel { .severity(ui::Severity::Warning) .child(Label::new(configuration_error.to_string())) .action_slot( - Button::new("settings", "Configure Provider") + Button::new("settings", "Configure Provider", cx) .style(ButtonStyle::Tinted(ui::TintColor::Warning)) .label_size(LabelSize::Small) .key_binding( @@ -2784,7 +2784,7 @@ impl AgentPanel { h_flex() .gap_1() .child( - Button::new("continue-conversation", "Continue") + Button::new("continue-conversation", "Continue", cx) .layer(ElevationIndex::ModalSurface) .label_size(LabelSize::Small) .key_binding( @@ -2802,7 +2802,7 @@ impl AgentPanel { ) .when(model.supports_burn_mode(), |this| { 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::Tinted(ui::TintColor::Accent)) .layer(ElevationIndex::ModalSurface) @@ -2873,7 +2873,7 @@ impl AgentPanel { thread: &Entity, cx: &mut Context, ) -> impl IntoElement { - Button::new("upgrade", "Upgrade") + Button::new("upgrade", "Upgrade", cx) .label_size(LabelSize::Small) .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .on_click(cx.listener({ @@ -2965,7 +2965,7 @@ impl AgentPanel { .size(IconSize::Small) .color(Color::Error); - let retry_button = Button::new("retry", "Retry") + let retry_button = Button::new("retry", "Retry", cx) .icon(IconName::RotateCw) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) @@ -3009,7 +3009,7 @@ impl AgentPanel { .size(IconSize::Small) .color(Color::Error); - let retry_button = Button::new("retry", "Retry") + let retry_button = Button::new("retry", "Retry", cx) .icon(IconName::RotateCw) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) @@ -3034,22 +3034,26 @@ impl AgentPanel { .primary_action(retry_button); if can_enable_burn_mode { - let burn_mode_button = Button::new("enable_burn_retry", "Enable Burn Mode and Retry") - .icon(IconName::ZedBurnMode) - .icon_position(IconPosition::Start) - .icon_size(IconSize::Small) - .label_size(LabelSize::Small) - .on_click({ - let thread = thread.clone(); - move |_, window, cx| { - thread.update(cx, |thread, cx| { - thread.clear_last_error(); - thread.thread().update(cx, |thread, cx| { - thread.enable_burn_mode_and_retry(Some(window.window_handle()), cx); + let burn_mode_button = + Button::new("enable_burn_retry", "Enable Burn Mode and Retry", cx) + .icon(IconName::ZedBurnMode) + .icon_position(IconPosition::Start) + .icon_size(IconSize::Small) + .label_size(LabelSize::Small) + .on_click({ + let thread = thread.clone(); + move |_, window, cx| { + thread.update(cx, |thread, cx| { + thread.clear_last_error(); + thread.thread().update(cx, |thread, cx| { + thread.enable_burn_mode_and_retry( + Some(window.window_handle()), + cx, + ); + }); }); - }); - } - }); + } + }); callout = callout.secondary_action(burn_mode_button); } diff --git a/crates/agent_ui/src/inline_prompt_editor.rs b/crates/agent_ui/src/inline_prompt_editor.rs index e6fca16984..d6fbd45ebb 100644 --- a/crates/agent_ui/src/inline_prompt_editor.rs +++ b/crates/agent_ui/src/inline_prompt_editor.rs @@ -478,7 +478,7 @@ impl PromptEditor { match codegen_status { CodegenStatus::Idle => { vec![ - Button::new("start", mode.start_label()) + Button::new("start", mode.start_label(), cx) .label_size(LabelSize::Small) .icon(IconName::Return) .icon_size(IconSize::XSmall) @@ -745,11 +745,11 @@ impl PromptEditor { h_flex() .gap_2() .child( - Button::new("dismiss", "Dismiss") + Button::new("dismiss", "Dismiss", cx) .style(ButtonStyle::Transparent) .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| { window.dispatch_action( Box::new(zed_actions::OpenAccountSettings), diff --git a/crates/agent_ui/src/language_model_selector.rs b/crates/agent_ui/src/language_model_selector.rs index 7121624c87..8b61a90237 100644 --- a/crates/agent_ui/src/language_model_selector.rs +++ b/crates/agent_ui/src/language_model_selector.rs @@ -548,7 +548,7 @@ impl PickerDelegate for LanguageModelPickerDelegate { .justify_between() .when(cx.has_flag::(), |this| { 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_size(IconSize::Small) .icon_color(Color::Muted) @@ -564,12 +564,13 @@ impl PickerDelegate for LanguageModelPickerDelegate { } else { "Try Pro" }, + cx, ) .on_click(|_, _, cx| cx.open_url(TRY_ZED_PRO_URL)), }) }) .child( - Button::new("configure", "Configure") + Button::new("configure", "Configure", cx) .icon(IconName::Settings) .icon_size(IconSize::Small) .icon_color(Color::Muted) diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index 4b6d51c4c1..6aea97db65 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -1074,7 +1074,7 @@ impl MessageEditor { ) .child(Divider::vertical().color(DividerColor::Border)) .child( - Button::new("reject-all-changes", "Reject All") + Button::new("reject-all-changes", "Reject All", cx) .label_size(LabelSize::Small) .disabled(pending_edits) .when(pending_edits, |this| { @@ -1094,7 +1094,7 @@ impl MessageEditor { })), ) .child( - Button::new("accept-all-changes", "Accept All") + Button::new("accept-all-changes", "Accept All", cx) .label_size(LabelSize::Small) .disabled(pending_edits) .when(pending_edits, |this| { @@ -1204,7 +1204,7 @@ impl MessageEditor { .gap_1() .visible_on_hover("edited-code") .child( - Button::new("review", "Review") + Button::new("review", "Review", cx) .label_size(LabelSize::Small) .on_click({ let buffer = buffer.clone(); @@ -1221,7 +1221,7 @@ impl MessageEditor { Divider::vertical().color(DividerColor::BorderVariant), ) .child( - Button::new("reject-file", "Reject") + Button::new("reject-file", "Reject", cx) .label_size(LabelSize::Small) .disabled(pending_edits) .on_click({ @@ -1236,7 +1236,7 @@ impl MessageEditor { }), ) .child( - Button::new("accept-file", "Accept") + Button::new("accept-file", "Accept", cx) .label_size(LabelSize::Small) .disabled(pending_edits) .on_click({ @@ -1332,7 +1332,7 @@ impl MessageEditor { .title(title) .description(description) .primary_action( - Button::new("start-new-thread", "Start New Thread") + Button::new("start-new-thread", "Start New Thread", cx) .label_size(LabelSize::Small) .on_click(cx.listener(|this, _, window, cx| { let from_thread_id = Some(this.thread.read(cx).id().clone()); diff --git a/crates/agent_ui/src/profile_selector.rs b/crates/agent_ui/src/profile_selector.rs index ddcb44d46b..368a21a8ef 100644 --- a/crates/agent_ui/src/profile_selector.rs +++ b/crates/agent_ui/src/profile_selector.rs @@ -167,7 +167,7 @@ impl Render for ProfileSelector { if configured_model.model.supports_tools() { let this = cx.entity().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) .color(Color::Muted) .icon(IconName::ChevronDown) @@ -201,7 +201,7 @@ impl Render for ProfileSelector { }) .into_any_element() } else { - Button::new("tools-not-supported-button", "Tools Unsupported") + Button::new("tools-not-supported-button", "Tools Unsupported", cx) .disabled(true) .label_size(LabelSize::Small) .color(Color::Muted) diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 49a37002f7..ca1edd2b09 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -1200,7 +1200,7 @@ impl TextThreadEditor { }) .children(match &message.status { MessageStatus::Error(error) => Some( - Button::new("show-error", "Error") + Button::new("show-error", "Error", cx) .color(Color::Error) .selected_label_color(Color::Error) .selected_icon_color(Color::Error) @@ -1920,7 +1920,7 @@ impl TextThreadEditor { None => (ButtonStyle::Filled, None), }; - Button::new("send_button", "Send") + Button::new("send_button", "Send", cx) .label_size(LabelSize::Small) .disabled(self.sending_disabled(cx)) .style(style) @@ -2124,14 +2124,16 @@ impl TextThreadEditor { h_flex() .justify_end() .mt_1() - .child(Button::new("subscribe", "Subscribe").on_click(cx.listener( - |this, _, _window, cx| { - this.last_error = None; - cx.open_url(&zed_urls::account_url(cx)); - cx.notify(); - }, - ))) - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( + .child( + Button::new("subscribe", "Subscribe", cx).on_click(cx.listener( + |this, _, _window, cx| { + this.last_error = None; + cx.open_url(&zed_urls::account_url(cx)); + cx.notify(); + }, + )), + ) + .child(Button::new("dismiss", "Dismiss", cx).on_click(cx.listener( |this, _, _window, cx| { this.last_error = None; cx.notify(); @@ -2165,17 +2167,14 @@ impl TextThreadEditor { .overflow_y_scroll() .child(Label::new(error_message.clone())), ) - .child( - h_flex() - .justify_end() - .mt_1() - .child(Button::new("dismiss", "Dismiss").on_click(cx.listener( - |this, _, _window, cx| { - this.last_error = None; - cx.notify(); - }, - ))), - ) + .child(h_flex().justify_end().mt_1().child( + Button::new("dismiss", "Dismiss", cx).on_click(cx.listener( + |this, _, _window, cx| { + this.last_error = None; + cx.notify(); + }, + )), + )) .into_any() } } diff --git a/crates/agent_ui/src/ui/agent_notification.rs b/crates/agent_ui/src/ui/agent_notification.rs index 68480c047f..84144b780f 100644 --- a/crates/agent_ui/src/ui/agent_notification.rs +++ b/crates/agent_ui/src/ui/agent_notification.rs @@ -171,7 +171,7 @@ impl Render for AgentNotification { .gap_1() .items_center() .child( - Button::new("open", "View Panel") + Button::new("open", "View Panel", cx) .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .full_width() .on_click({ @@ -180,11 +180,15 @@ impl Render for AgentNotification { }) }), ) - .child(Button::new("dismiss", "Dismiss").full_width().on_click({ - cx.listener(move |_, _event, _, cx| { - cx.emit(AgentNotificationEvent::Dismissed); - }) - })), + .child( + Button::new("dismiss", "Dismiss", cx) + .full_width() + .on_click({ + cx.listener(move |_, _event, _, cx| { + cx.emit(AgentNotificationEvent::Dismissed); + }) + }), + ), ) } } diff --git a/crates/agent_ui/src/ui/end_trial_upsell.rs b/crates/agent_ui/src/ui/end_trial_upsell.rs index 3a8a119800..154f704ef3 100644 --- a/crates/agent_ui/src/ui/end_trial_upsell.rs +++ b/crates/agent_ui/src/ui/end_trial_upsell.rs @@ -35,7 +35,7 @@ impl RenderOnce for EndTrialUpsell { ) .child(plan_definitions.pro_plan(false)) .child( - Button::new("cta-button", "Upgrade to Zed Pro") + Button::new("cta-button", "Upgrade to Zed Pro", cx) .full_width() .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .on_click(move |_, _window, cx| { diff --git a/crates/agent_ui/src/ui/onboarding_modal.rs b/crates/agent_ui/src/ui/onboarding_modal.rs index b8b038bdfc..961608b647 100644 --- a/crates/agent_ui/src/ui/onboarding_modal.rs +++ b/crates/agent_ui/src/ui/onboarding_modal.rs @@ -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) .style(ButtonStyle::Tinted(TintColor::Accent)) .full_width() .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_size(IconSize::Indicator) .icon_color(Color::Muted) diff --git a/crates/agent_ui/src/ui/preview/usage_callouts.rs b/crates/agent_ui/src/ui/preview/usage_callouts.rs index eef878a9d1..0560bab1d8 100644 --- a/crates/agent_ui/src/ui/preview/usage_callouts.rs +++ b/crates/agent_ui/src/ui/preview/usage_callouts.rs @@ -99,7 +99,7 @@ impl RenderOnce for UsageCallout { .title(title) .description(message) .primary_action( - Button::new("upgrade", button_text) + Button::new("upgrade", button_text, cx) .label_size(LabelSize::Small) .on_click(move |_, _, cx| { cx.open_url(&url); diff --git a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs index b55ad4c895..16288e8f88 100644 --- a/crates/ai_onboarding/src/agent_api_keys_onboarding.rs +++ b/crates/ai_onboarding/src/agent_api_keys_onboarding.rs @@ -130,7 +130,7 @@ impl RenderOnce for ApiKeysWithoutProviders { "Add your own keys to use AI without signing in.", ))) .child( - Button::new("configure-providers", "Configure Providers") + Button::new("configure-providers", "Configure Providers", cx) .full_width() .style(ButtonStyle::Outlined) .on_click(move |_, window, cx| { diff --git a/crates/ai_onboarding/src/ai_onboarding.rs b/crates/ai_onboarding/src/ai_onboarding.rs index 75177d4bd2..3e7982d433 100644 --- a/crates/ai_onboarding/src/ai_onboarding.rs +++ b/crates/ai_onboarding/src/ai_onboarding.rs @@ -94,7 +94,7 @@ impl ZedAiOnboarding { self } - fn render_accept_terms_of_service(&self) -> AnyElement { + fn render_accept_terms_of_service(&self, cx: &mut App) -> AnyElement { v_flex() .gap_1() .w_full() @@ -105,7 +105,7 @@ impl ZedAiOnboarding { .mb_2(), ) .child( - Button::new("terms_of_service", "Review Terms of Service") + Button::new("terms_of_service", "Review Terms of Service", cx) .full_width() .style(ButtonStyle::Outlined) .icon(IconName::ArrowUpRight) @@ -117,7 +117,7 @@ impl ZedAiOnboarding { }), ) .child( - Button::new("accept_terms", "Accept") + Button::new("accept_terms", "Accept", cx) .full_width() .style(ButtonStyle::Tinted(TintColor::Accent)) .on_click({ @@ -130,7 +130,7 @@ impl ZedAiOnboarding { .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 plan_definitions = PlanDefinitions; @@ -144,7 +144,7 @@ impl ZedAiOnboarding { ) .child(plan_definitions.pro_plan(false)) .child( - Button::new("sign_in", "Try Zed Pro for Free") + Button::new("sign_in", "Try Zed Pro for Free", cx) .disabled(signing_in) .full_width() .style(ButtonStyle::Tinted(ui::TintColor::Accent)) @@ -187,7 +187,7 @@ impl ZedAiOnboarding { ) .child(plan_definitions.pro_plan(true)) .child( - Button::new("pro", "Get Started") + Button::new("pro", "Get Started", cx) .full_width() .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .on_click(move |_, _window, cx| { @@ -268,7 +268,7 @@ impl ZedAiOnboarding { ) .child(plan_definitions.pro_trial(true)) .child( - Button::new("pro", "Start Free Trial") + Button::new("pro", "Start Free Trial", cx) .full_width() .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .on_click(move |_, _window, cx| { @@ -320,7 +320,7 @@ impl ZedAiOnboarding { .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; v_flex() @@ -333,7 +333,7 @@ impl ZedAiOnboarding { ) .child(plan_definitions.pro_plan(false)) .child( - Button::new("pro", "Continue with Zed Pro") + Button::new("pro", "Continue with Zed Pro", cx) .full_width() .style(ButtonStyle::Outlined) .on_click({ @@ -358,7 +358,7 @@ impl RenderOnce for ZedAiOnboarding { Some(Plan::ZedPro) => self.render_pro_plan_state(cx), } } else { - self.render_accept_terms_of_service() + self.render_accept_terms_of_service(cx) } } else { self.render_sign_in_disclaimer(cx) diff --git a/crates/ai_onboarding/src/ai_upsell_card.rs b/crates/ai_onboarding/src/ai_upsell_card.rs index e9639ca075..4aabc56b05 100644 --- a/crates/ai_onboarding/src/ai_upsell_card.rs +++ b/crates/ai_onboarding/src/ai_upsell_card.rs @@ -186,7 +186,7 @@ impl RenderOnce for AiUpsellCard { ) .child(plan_definitions.pro_plan(true)) .child( - Button::new("pro", "Get Started") + Button::new("pro", "Get Started", cx) .full_width() .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .on_click(move |_, _window, cx| { @@ -209,19 +209,25 @@ impl RenderOnce for AiUpsellCard { .child( footer_container .child( - Button::new("start_trial", "Start 14-day Free Pro Trial") - .full_width() - .style(ButtonStyle::Tinted(ui::TintColor::Accent)) - .when_some(self.tab_index, |this, tab_index| { - this.tab_index(tab_index) - }) - .on_click(move |_, _window, cx| { + Button::new( + "start_trial", + "Start 14-day Free Pro Trial", + cx, + ) + .full_width() + .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!( "Start Trial Clicked", state = "post-sign-in" ); cx.open_url(&zed_urls::start_trial_url(cx)) - }), + }, + ), ) .child( Label::new("No credit card required") @@ -261,7 +267,7 @@ impl RenderOnce for AiUpsellCard { ) .child(plans_section) .child( - Button::new("sign_in", "Sign In") + Button::new("sign_in", "Sign In", cx) .full_width() .style(ButtonStyle::Tinted(ui::TintColor::Accent)) .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index)) diff --git a/crates/ai_onboarding/src/edit_prediction_onboarding_content.rs b/crates/ai_onboarding/src/edit_prediction_onboarding_content.rs index e883d8da8c..dca9ab6422 100644 --- a/crates/ai_onboarding/src/edit_prediction_onboarding_content.rs +++ b/crates/ai_onboarding/src/edit_prediction_onboarding_content.rs @@ -50,6 +50,7 @@ impl Render for EditPredictionOnboarding { } else { "Configure Copilot" }, + cx, ) .full_width() .style(ButtonStyle::Outlined) diff --git a/crates/assistant_tools/src/find_path_tool.rs b/crates/assistant_tools/src/find_path_tool.rs index 6b62638a4c..3a8d6ca133 100644 --- a/crates/assistant_tools/src/find_path_tool.rs +++ b/crates/assistant_tools/src/find_path_tool.rs @@ -256,7 +256,7 @@ impl ToolCard for FindPathToolCard { let workspace_clone = workspace.clone(); 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_size(IconSize::Small) .icon_position(IconPosition::End) diff --git a/crates/assistant_tools/src/web_search_tool.rs b/crates/assistant_tools/src/web_search_tool.rs index 47a6958b7a..ebd7477e81 100644 --- a/crates/assistant_tools/src/web_search_tool.rs +++ b/crates/assistant_tools/src/web_search_tool.rs @@ -174,7 +174,7 @@ impl ToolCard for WebSearchToolCard { let title = result.title.clone(); let url = SharedString::from(result.url.clone()); - Button::new(("result", index), title) + Button::new(("result", index), title, cx) .label_size(LabelSize::Small) .color(Color::Muted) .icon(IconName::ArrowUpRight) diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 51d9f003f8..e5f31bc603 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -987,7 +987,7 @@ impl Render for ChatPanel { ) .child( div().pt_1().w_full().items_center().child( - Button::new("toggle-collab", "Open") + Button::new("toggle-collab", "Open", cx) .full_width() .key_binding(KeyBinding::for_action( &collab_panel::ToggleFocus, diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 430b447580..7985c0c9a9 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -2307,7 +2307,7 @@ impl CollabPanel { v_flex() .gap_2() .child( - Button::new("sign_in", "Sign in") + Button::new("sign_in", "Sign in", cx) .icon_color(Color::Muted) .icon(IconName::Github) .icon_position(IconPosition::Start) diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index c0d3130ee9..a88f91da2d 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -177,7 +177,7 @@ impl Render for ChannelModal { )) .children( Some( - Button::new("copy-link", "Copy Link") + Button::new("copy-link", "Copy Link", cx) .label_size(LabelSize::Small) .on_click(cx.listener(move |this, _, _, cx| { if let Some(channel) = this diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 3a280ff667..15ccfd452c 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -319,20 +319,22 @@ impl NotificationPanel { h_flex() .flex_grow() .justify_end() - .child(Button::new("decline", "Decline").on_click({ - let notification = notification.clone(); - let entity = cx.entity().clone(); - move |_, _, cx| { - entity.update(cx, |this, cx| { - this.respond_to_notification( - notification.clone(), - false, - cx, - ) - }); - } - })) - .child(Button::new("accept", "Accept").on_click({ + .child(Button::new("decline", "Decline", cx).on_click( + { + let notification = notification.clone(); + let entity = cx.entity().clone(); + move |_, _, cx| { + entity.update(cx, |this, cx| { + this.respond_to_notification( + notification.clone(), + false, + cx, + ) + }); + } + }, + )) + .child(Button::new("accept", "Accept", cx).on_click({ let notification = notification.clone(); let entity = cx.entity().clone(); move |_, _, cx| { @@ -631,7 +633,7 @@ impl Render for NotificationPanel { .gap_2() .p_4() .child( - Button::new("connect_prompt_button", "Connect") + Button::new("connect_prompt_button", "Connect", cx) .icon_color(Color::Muted) .icon(IconName::Github) .icon_position(IconPosition::Start) diff --git a/crates/collab_ui/src/notifications/incoming_call_notification.rs b/crates/collab_ui/src/notifications/incoming_call_notification.rs index 0bf71ce614..3a367bcc20 100644 --- a/crates/collab_ui/src/notifications/incoming_call_notification.rs +++ b/crates/collab_ui/src/notifications/incoming_call_notification.rs @@ -117,11 +117,11 @@ impl Render for IncomingCallNotification { div().size_full().font(ui_font).child( CollabNotification::new( 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(); move |_, _, cx| state.respond(true, cx) }), - Button::new("decline", "Decline").on_click({ + Button::new("decline", "Decline", cx).on_click({ let state = self.state.clone(); move |_, _, cx| state.respond(false, cx) }), diff --git a/crates/collab_ui/src/notifications/project_shared_notification.rs b/crates/collab_ui/src/notifications/project_shared_notification.rs index b21a2dfcb7..a565113f40 100644 --- a/crates/collab_ui/src/notifications/project_shared_notification.rs +++ b/crates/collab_ui/src/notifications/project_shared_notification.rs @@ -126,10 +126,12 @@ impl Render for ProjectSharedNotification { div().size_full().font(ui_font).child( CollabNotification::new( self.owner.avatar_uri.clone(), - Button::new("open", "Open").on_click(cx.listener(move |this, _event, _, cx| { - this.join(cx); - })), - Button::new("dismiss", "Dismiss").on_click(cx.listener( + Button::new("open", "Open", cx).on_click(cx.listener( + move |this, _event, _, cx| { + this.join(cx); + }, + )), + Button::new("dismiss", "Dismiss", cx).on_click(cx.listener( move |this, _event, _, cx| { this.dismiss(cx); }, diff --git a/crates/collab_ui/src/notifications/stories/collab_notification.rs b/crates/collab_ui/src/notifications/stories/collab_notification.rs index 8dc602a320..42ba2c1847 100644 --- a/crates/collab_ui/src/notifications/stories/collab_notification.rs +++ b/crates/collab_ui/src/notifications/stories/collab_notification.rs @@ -18,8 +18,8 @@ impl Render for CollabNotificationStory { window_container(400., 72.).child( CollabNotification::new( "https://avatars.githubusercontent.com/u/1486634?v=4", - Button::new("accept", "Accept"), - Button::new("decline", "Decline"), + Button::new("accept", "Accept", cx), + Button::new("decline", "Decline", cx), ) .child( v_flex() @@ -35,8 +35,8 @@ impl Render for CollabNotificationStory { window_container(400., 72.).child( CollabNotification::new( "https://avatars.githubusercontent.com/u/1714999?v=4", - Button::new("open", "Open"), - Button::new("dismiss", "Dismiss"), + Button::new("open", "Open", cx), + Button::new("dismiss", "Dismiss", cx), ) .child(Label::new("iamnbutler")) .child(Label::new("is sharing a project in Zed:")) diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index 464a114d4e..54ba959a79 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -264,7 +264,7 @@ impl CopilotCodeVerification { .size(ui::LabelSize::Small), ) .child( - Button::new("connect-button", connect_button_label) + Button::new("connect-button", connect_button_label, cx) .on_click({ let verification_uri = data.verification_uri.clone(); cx.listener(move |this, _, _window, cx| { @@ -276,7 +276,7 @@ impl CopilotCodeVerification { .style(ButtonStyle::Filled), ) .child( - Button::new("copilot-enable-cancel-button", "Cancel") + Button::new("copilot-enable-cancel-button", "Cancel", cx) .full_width() .on_click(cx.listener(|_, _, _, cx| { 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.", )) .child( - Button::new("copilot-enabled-done-button", "Done") + Button::new("copilot-enabled-done-button", "Done", cx) .full_width() .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.", ).color(Color::Warning)) .child( - Button::new("copilot-subscribe-button", "Subscribe on GitHub") + Button::new("copilot-subscribe-button", "Subscribe on GitHub", cx) .full_width() .on_click(|_, _, cx| cx.open_url(COPILOT_SIGN_UP_URL)), ) .child( - Button::new("copilot-subscribe-cancel-button", "Cancel") + Button::new("copilot-subscribe-cancel-button", "Cancel", cx) .full_width() .on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))), ) diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index b806381d25..ea45c08f32 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -535,6 +535,7 @@ impl Render for DapLogToolbarItemView { )) }) .unwrap_or_else(|| "No adapter selected".into()), + cx, )) .menu(move |mut window, cx| { let log_view = log_view.clone(); @@ -632,7 +633,7 @@ impl Render for DapLogToolbarItemView { .child( div() .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| { if let Some(log_view) = this.log_view.as_ref() { log_view.update(cx, |log_view, cx| { diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 1d44c5c244..8876170672 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -1715,7 +1715,7 @@ impl Render for DebugPanel { .justify_center() .gap_2() .child( - Button::new("spawn-new-session-empty-state", "New Session") + Button::new("spawn-new-session-empty-state", "New Session", cx) .icon(IconName::Plus) .icon_size(IconSize::XSmall) .icon_color(Color::Muted) @@ -1725,7 +1725,7 @@ impl Render for DebugPanel { }), ) .child( - Button::new("edit-debug-settings", "Edit debug.json") + Button::new("edit-debug-settings", "Edit debug.json", cx) .icon(IconName::Code) .icon_size(IconSize::XSmall) .color(Color::Muted) @@ -1739,7 +1739,7 @@ impl Render for DebugPanel { }), ) .child( - Button::new("open-debugger-docs", "Debugger Docs") + Button::new("open-debugger-docs", "Debugger Docs", cx) .icon(IconName::Book) .color(Color::Muted) .icon_size(IconSize::XSmall) @@ -1751,6 +1751,7 @@ impl Render for DebugPanel { Button::new( "spawn-new-session-install-extensions", "Debugger Extensions", + cx, ) .icon(IconName::Blocks) .color(Color::Muted) diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index 4ac8e371a1..651b86e7b3 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -703,7 +703,7 @@ impl Render for NewProcessModal { container .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| { this.save_debug_scenario(window, cx); })) @@ -719,7 +719,7 @@ impl Render for NewProcessModal { ), ) .child( - Button::new("debugger-spawn", "Start") + Button::new("debugger-spawn", "Start", cx) .on_click(cx.listener(|this, _, window, cx| { this.start_new_session(window, cx) })) @@ -751,7 +751,7 @@ impl Render for NewProcessModal { .child(div().children( KeyBinding::for_action(&*secondary_action, window, cx).map( |keybind| { - Button::new("edit-attach-task", "Edit in debug.json") + Button::new("edit-attach-task", "Edit in debug.json", cx) .label_size(LabelSize::Small) .key_binding(keybind) .on_click(move |_, window, cx| { @@ -1389,7 +1389,7 @@ impl PickerDelegate for DebugDelegate { .children({ let action = menu::SecondaryConfirm.boxed_clone(); 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) .key_binding(keybind) .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() { let action = picker::ConfirmInput { secondary: false }.boxed_clone(); 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) .on_click(move |_, window, cx| { window.dispatch_action(action.boxed_clone(), cx) @@ -1415,7 +1415,7 @@ impl PickerDelegate for DebugDelegate { let run_entry_label = if is_recent_selected { "Rerun" } else { "Spawn" }; - Button::new("spawn", run_entry_label) + Button::new("spawn", run_entry_label, cx) .key_binding(keybind) .on_click(|_, window, cx| { window.dispatch_action(menu::Confirm.boxed_clone(), cx); diff --git a/crates/debugger_ui/src/onboarding_modal.rs b/crates/debugger_ui/src/onboarding_modal.rs index 2a9f68d0c9..35e7e9741b 100644 --- a/crates/debugger_ui/src/onboarding_modal.rs +++ b/crates/debugger_ui/src/onboarding_modal.rs @@ -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) .style(ButtonStyle::Tinted(TintColor::Accent)) .full_width() .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_size(IconSize::Indicator) .icon_color(Color::Muted) diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e7660920da..289bec65e2 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -128,12 +128,12 @@ impl Render for ProjectDiagnosticsEditor { self.summary.warning_count, plural_suffix ); this.child( - Button::new("diagnostics-show-warning-label", label).on_click(cx.listener( - |this, _, window, cx| { + Button::new("diagnostics-show-warning-label", label, cx).on_click( + cx.listener(|this, _, window, cx| { this.toggle_warnings(&Default::default(), window, cx); cx.notify(); - }, - )), + }), + ), ) }) } else { diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index 7ac6d101f3..4ae06ede0c 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -74,7 +74,7 @@ impl Render for DiagnosticIndicator { let status = if let Some(diagnostic) = &self.current_diagnostic { let message = diagnostic.message.split('\n').next().unwrap().to_string(); Some( - Button::new("diagnostic_message", message) + Button::new("diagnostic_message", message, cx) .label_size(LabelSize::Small) .tooltip(|window, cx| { Tooltip::for_action( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8a9398e71f..c8b24c5279 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -23754,10 +23754,10 @@ impl Render for MissingEditPredictionKeybindingTooltip { .gap_1() .items_end() .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) })) - .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"); })), ) @@ -23804,7 +23804,7 @@ fn render_diff_hunk_controls( .block_mouse_except_scroll() .shadow_md() .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 }) .tooltip({ let focus_handle = editor.focus_handle(cx); @@ -23831,7 +23831,7 @@ fn render_diff_hunk_controls( } }) } 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 }) .tooltip({ let focus_handle = editor.focus_handle(cx); @@ -23859,7 +23859,7 @@ fn render_diff_hunk_controls( }) }) .child( - Button::new(("restore", row as u64), "Restore") + Button::new(("restore", row as u64), "Restore", cx) .tooltip({ let focus_handle = editor.focus_handle(cx); move |window, cx| { diff --git a/crates/extensions_ui/src/components/feature_upsell.rs b/crates/extensions_ui/src/components/feature_upsell.rs index 573b0b992d..931beeb907 100644 --- a/crates/extensions_ui/src/components/feature_upsell.rs +++ b/crates/extensions_ui/src/components/feature_upsell.rs @@ -56,7 +56,7 @@ impl RenderOnce for FeatureUpsell { self.docs_url, |el, docs_url| { el.child( - Button::new("open_docs", "View Documentation") + Button::new("open_docs", "View Documentation", cx) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) .icon_position(IconPosition::End) diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index fe3e94f5c2..03b82f225f 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -596,6 +596,7 @@ impl ExtensionsPage { Button::new( SharedString::from(format!("rebuild-{}", extension.id)), "Rebuild", + cx, ) .color(Color::Accent) .disabled(matches!(status, ExtensionStatus::Upgrading)) @@ -609,7 +610,7 @@ impl ExtensionsPage { }), ) .child( - Button::new(SharedString::from(extension.id.clone()), "Uninstall") + Button::new(SharedString::from(extension.id.clone()), "Uninstall", cx) .color(Color::Accent) .disabled(matches!(status, ExtensionStatus::Removing)) .on_click({ @@ -626,6 +627,7 @@ impl ExtensionsPage { Button::new( SharedString::from(format!("configure-{}", extension.id)), "Configure", + cx, ) .color(Color::Accent) .disabled(matches!(status, ExtensionStatus::Installing)) @@ -947,6 +949,7 @@ impl ExtensionsPage { install_or_uninstall: Button::new( SharedString::from(extension.id.clone()), "Install", + cx, ), configure: None, upgrade: None, @@ -963,6 +966,7 @@ impl ExtensionsPage { install_or_uninstall: Button::new( SharedString::from(extension.id.clone()), "Install", + cx, ) .on_click({ let extension_id = extension.id.clone(); @@ -980,6 +984,7 @@ impl ExtensionsPage { install_or_uninstall: Button::new( SharedString::from(extension.id.clone()), "Install", + cx, ) .disabled(true), configure: None, @@ -989,23 +994,27 @@ impl ExtensionsPage { install_or_uninstall: Button::new( SharedString::from(extension.id.clone()), "Uninstall", + cx, ) .disabled(true), configure: is_configurable.then(|| { Button::new( SharedString::from(format!("configure-{}", extension.id)), "Configure", + cx, ) .disabled(true) }), 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 { install_or_uninstall: Button::new( SharedString::from(extension.id.clone()), "Uninstall", + cx, ) .on_click({ let extension_id = extension.id.clone(); @@ -1022,6 +1031,7 @@ impl ExtensionsPage { Button::new( SharedString::from(format!("configure-{}", extension.id)), "Configure", + cx, ) .on_click({ let extension_id = extension.id.clone(); @@ -1047,7 +1057,7 @@ impl ExtensionsPage { None } else { Some( - Button::new(SharedString::from(extension.id.clone()), "Upgrade") + Button::new(SharedString::from(extension.id.clone()), "Upgrade", cx) .when(!is_compatible, |upgrade_button| { upgrade_button.disabled(true).tooltip({ let version = extension.manifest.version.clone(); @@ -1085,12 +1095,14 @@ impl ExtensionsPage { install_or_uninstall: Button::new( SharedString::from(extension.id.clone()), "Uninstall", + cx, ) .disabled(true), configure: is_configurable.then(|| { Button::new( SharedString::from(format!("configure-{}", extension.id)), "Configure", + cx, ) .disabled(true) }), @@ -1394,7 +1406,7 @@ impl Render for ExtensionsPage { .justify_between() .child(Headline::new("Extensions").size(HeadlineSize::XLarge)) .child( - Button::new("install-dev-extension", "Install Dev Extension") + Button::new("install-dev-extension", "Install Dev Extension", cx) .style(ButtonStyle::Filled) .size(ButtonSize::Large) .on_click(|_event, window, cx| { @@ -1470,7 +1482,7 @@ impl Render for ExtensionsPage { .border_color(cx.theme().colors().border_variant) .overflow_x_scroll() .child( - Button::new("filter-all-categories", "All") + Button::new("filter-all-categories", "All", cx) .when(self.provides_filter.is_none(), |button| { button.style(ButtonStyle::Filled) }) @@ -1493,7 +1505,7 @@ impl Render for ExtensionsPage { let button_id = SharedString::from(format!("filter-category-{}", label)); Some( - Button::new(button_id, label) + Button::new(button_id, label, cx) .style(if self.provides_filter == Some(provides) { ButtonStyle::Filled } else { diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index beb879efe7..f671807787 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -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( - Button::new("file-a-bug-report", "File a Bug Report") + Button::new("file-a-bug-report", "File a Bug Report", cx) .full_width() .icon(IconName::Debug) .icon_size(IconSize::XSmall) @@ -79,7 +79,7 @@ impl Render for FeedbackModal { })), ) .child( - Button::new("request-a-feature", "Request a Feature") + Button::new("request-a-feature", "Request a Feature", cx) .full_width() .icon(IconName::Sparkle) .icon_size(IconSize::XSmall) @@ -90,7 +90,7 @@ impl Render for FeedbackModal { })), ) .child( - Button::new("send-us_an-email", "Send an Email") + Button::new("send-us_an-email", "Send an Email", cx) .full_width() .icon(IconName::Envelope) .icon_size(IconSize::XSmall) @@ -101,7 +101,7 @@ impl Render for FeedbackModal { })), ) .child( - Button::new("zed_repository", "GitHub Repository") + Button::new("zed_repository", "GitHub Repository", cx) .full_width() .icon(IconName::Github) .icon_size(IconSize::XSmall) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index c6997ccdc0..be790195e6 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1779,7 +1779,7 @@ impl PickerDelegate for FileFinderDelegate { }), ) .child( - Button::new("open-selection", "Open") + Button::new("open-selection", "Open", cx) .key_binding( KeyBinding::for_action_in( &menu::Confirm, diff --git a/crates/git_ui/src/blame_ui.rs b/crates/git_ui/src/blame_ui.rs index f910de7bbe..d00e6f7175 100644 --- a/crates/git_ui/src/blame_ui.rs +++ b/crates/git_ui/src/blame_ui.rs @@ -301,6 +301,7 @@ impl BlameRenderer for GitBlameRenderer { Button::new( "pull-request-button", format!("#{}", pr.number), + cx, ) .color(Color::Muted) .icon(IconName::PullRequest) @@ -318,6 +319,7 @@ impl BlameRenderer for GitBlameRenderer { Button::new( "commit-sha-button", short_commit_id.clone(), + cx, ) .style(ButtonStyle::Subtle) .color(Color::Muted) diff --git a/crates/git_ui/src/commit_modal.rs b/crates/git_ui/src/commit_modal.rs index 5e7430ebc6..8617875ca4 100644 --- a/crates/git_ui/src/commit_modal.rs +++ b/crates/git_ui/src/commit_modal.rs @@ -363,7 +363,7 @@ impl CommitModal { .map(|b| b.name().to_owned()) .unwrap_or_else(|| "".to_owned()); - let branch_picker_button = panel_button(branch) + let branch_picker_button = panel_button(branch, cx) .icon(IconName::GitBranch) .icon_size(IconSize::Small) .icon_color(Color::Placeholder) diff --git a/crates/git_ui/src/commit_tooltip.rs b/crates/git_ui/src/commit_tooltip.rs index 00ab911610..2b936195ef 100644 --- a/crates/git_ui/src/commit_tooltip.rs +++ b/crates/git_ui/src/commit_tooltip.rs @@ -283,6 +283,7 @@ impl Render for CommitTooltip { Button::new( "pull-request-button", format!("#{}", pr.number), + cx, ) .color(Color::Muted) .icon(IconName::PullRequest) @@ -300,6 +301,7 @@ impl Render for CommitTooltip { Button::new( "commit-sha-button", short_commit_id.clone(), + cx, ) .style(ButtonStyle::Subtle) .color(Color::Muted) diff --git a/crates/git_ui/src/conflict_view.rs b/crates/git_ui/src/conflict_view.rs index 0bbb9411be..f7a6d49f05 100644 --- a/crates/git_ui/src/conflict_view.rs +++ b/crates/git_ui/src/conflict_view.rs @@ -394,7 +394,7 @@ fn render_conflict_buttons( .gap_1() .bg(cx.theme().colors().editor_background) .child( - Button::new("head", "Use HEAD") + Button::new("head", "Use HEAD", cx) .label_size(LabelSize::Small) .on_click({ let editor = editor.clone(); @@ -414,7 +414,7 @@ fn render_conflict_buttons( }), ) .child( - Button::new("origin", "Use Origin") + Button::new("origin", "Use Origin", cx) .label_size(LabelSize::Small) .on_click({ let editor = editor.clone(); @@ -434,7 +434,7 @@ fn render_conflict_buttons( }), ) .child( - Button::new("both", "Use Both") + Button::new("both", "Use Both", cx) .label_size(LabelSize::Small) .on_click({ let editor = editor.clone(); diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index de308b9dde..5cc38a1f53 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -3327,7 +3327,7 @@ impl GitPanel { .px_2() .justify_between() .child( - panel_button(change_string) + panel_button(change_string, cx) .color(Color::Muted) .tooltip(Tooltip::for_action_title_in( "Open Diff", @@ -3345,7 +3345,7 @@ impl GitPanel { .gap_1() .child(self.render_overflow_menu("overflow_menu")) .child( - panel_filled_button(text) + panel_filled_button(text, cx) .tooltip(Tooltip::for_action_title_in( tooltip, action.as_ref(), @@ -3604,7 +3604,7 @@ impl GitPanel { ), ) .child( - panel_button("Cancel") + panel_button("Cancel", cx) .size(ButtonSize::Default) .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(); (worktree_count > 0 && self.active_repository.is_none()).then(|| { h_flex().w_full().justify_around().child( - panel_filled_button("Initialize Repository") + panel_filled_button("Initialize Repository", cx) .tooltip(Tooltip::for_action_title_in( "git init", &git::Init, @@ -4841,7 +4841,7 @@ impl RenderOnce for PanelRepoFooter { 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) .size(ButtonSize::None) .label_size(LabelSize::Small) @@ -4862,7 +4862,7 @@ impl RenderOnce for PanelRepoFooter { .anchor(Corner::BottomLeft) .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) .size(ButtonSize::None) .label_size(LabelSize::Small) diff --git a/crates/git_ui/src/onboarding.rs b/crates/git_ui/src/onboarding.rs index d1709e043b..e39d2bc4f3 100644 --- a/crates/git_ui/src/onboarding.rs +++ b/crates/git_ui/src/onboarding.rs @@ -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) .style(ButtonStyle::Tinted(TintColor::Accent)) .full_width() .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_size(IconSize::Indicator) .icon_color(Color::Muted) diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index d6a4e27286..078f142f1e 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -759,7 +759,7 @@ impl Render for ProjectDiff { }) .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) .key_binding(KeyBinding::for_action_in( &CloseActiveItem::default(), @@ -936,7 +936,7 @@ impl Render for ProjectDiffToolbar { h_group_sm() .when(button_states.selection, |el| { el.child( - Button::new("stage", "Toggle Staged") + Button::new("stage", "Toggle Staged", cx) .tooltip(Tooltip::for_action_title_in( "Toggle Staged", &ToggleStaged, @@ -950,7 +950,7 @@ impl Render for ProjectDiffToolbar { }) .when(!button_states.selection, |el| { el.child( - Button::new("stage", "Stage") + Button::new("stage", "Stage", cx) .tooltip(Tooltip::for_action_title_in( "Stage and go to next hunk", &StageAndNext, @@ -961,7 +961,7 @@ impl Render for ProjectDiffToolbar { })), ) .child( - Button::new("unstage", "Unstage") + Button::new("unstage", "Unstage", cx) .tooltip(Tooltip::for_action_title_in( "Unstage and go to next hunk", &UnstageAndNext, @@ -1011,7 +1011,7 @@ impl Render for ProjectDiffToolbar { button_states.unstage_all && !button_states.stage_all, |el| { el.child( - Button::new("unstage-all", "Unstage All") + Button::new("unstage-all", "Unstage All", cx) .tooltip(Tooltip::for_action_title_in( "Unstage all changes", &UnstageAll, @@ -1030,7 +1030,7 @@ impl Render for ProjectDiffToolbar { // todo make it so that changing to say "Unstaged" // doesn't change the position. div().child( - Button::new("stage-all", "Stage All") + Button::new("stage-all", "Stage All", cx) .disabled(!button_states.stage_all) .tooltip(Tooltip::for_action_title_in( "Stage all changes", @@ -1045,7 +1045,7 @@ impl Render for ProjectDiffToolbar { }, ) .child( - Button::new("commit", "Commit") + Button::new("commit", "Commit", cx) .tooltip(Tooltip::for_action_title_in( "Commit", &Commit, diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 29064eb29c..1ad6bfadaf 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -219,7 +219,7 @@ impl Render for CursorPosition { let context = self.context.clone(); el.child( - Button::new("go-to-line-column", text) + Button::new("go-to-line-column", text, cx) .label_size(LabelSize::Small) .on_click(cx.listener(|this, _, window, cx| { if let Some(workspace) = this.workspace.upgrade() { diff --git a/crates/image_viewer/src/image_info.rs b/crates/image_viewer/src/image_info.rs index 70a92736aa..c79833a665 100644 --- a/crates/image_viewer/src/image_info.rs +++ b/crates/image_viewer/src/image_info.rs @@ -78,7 +78,7 @@ impl Render for ImageInfo { ); div().child( - Button::new("image-metadata", components.join(" • ")).label_size(LabelSize::Small), + Button::new("image-metadata", components.join(" • "), cx).label_size(LabelSize::Small), ) } } diff --git a/crates/language_models/src/provider/anthropic.rs b/crates/language_models/src/provider/anthropic.rs index ef21e85f71..a5b59a5774 100644 --- a/crates/language_models/src/provider/anthropic.rs +++ b/crates/language_models/src/provider/anthropic.rs @@ -1017,13 +1017,14 @@ impl Render for ConfigurationView { List::new() .child( InstructionListItem::new( + cx, "Create one by visiting", Some("Anthropic's settings"), Some("https://console.anthropic.com/settings/keys") ) ) .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( @@ -1066,7 +1067,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-key", "Reset Key") + Button::new("reset-key", "Reset Key", cx) .label_size(LabelSize::Small) .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) diff --git a/crates/language_models/src/provider/bedrock.rs b/crates/language_models/src/provider/bedrock.rs index 6df96c5c56..36401ab43b 100644 --- a/crates/language_models/src/provider/bedrock.rs +++ b/crates/language_models/src/provider/bedrock.rs @@ -1232,7 +1232,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-key", "Reset Key") + Button::new("reset-key", "Reset Key", cx) .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) @@ -1257,6 +1257,7 @@ impl Render for ConfigurationView { List::new() .child( InstructionListItem::new( + cx, "Grant permissions to the strategy you'll use according to the:", Some("Prerequisites"), Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"), @@ -1264,6 +1265,7 @@ impl Render for ConfigurationView { ) .child( InstructionListItem::new( + cx, "Select the models you would like access to:", Some("Bedrock Model Catalog"), Some("https://us-east-1.console.aws.amazon.com/bedrock/home?region=us-east-1#/modelaccess"), @@ -1365,19 +1367,23 @@ impl ConfigurationView { .child( List::new() .child(InstructionListItem::new( + cx, "Create an IAM user in the AWS console with programmatic access", Some("IAM Console"), Some("https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1#/users"), )) .child(InstructionListItem::new( + cx, "Attach the necessary Bedrock permissions to this ", Some("user"), Some("https://docs.aws.amazon.com/bedrock/latest/userguide/inference-prereq.html"), )) .child(InstructionListItem::text_only( + cx, "Copy the access key ID and secret access key when provided", )) .child(InstructionListItem::text_only( + cx, "Enter these credentials below", )), ) diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index ff8048040e..eb18364ad8 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -410,12 +410,17 @@ impl LanguageModelProvider for CloudLanguageModelProvider { return None; } Some( - render_accept_terms(view, state.accept_terms_of_service_task.is_some(), { - let state = self.state.clone(); - move |_window, cx| { - state.update(cx, |state, cx| state.accept_terms_of_service(cx)); - } - }) + render_accept_terms( + view, + state.accept_terms_of_service_task.is_some(), + { + let state = self.state.clone(); + move |_window, cx| { + state.update(cx, |state, cx| state.accept_terms_of_service(cx)); + } + }, + cx, + ) .into_any_element(), ) } @@ -429,11 +434,12 @@ fn render_accept_terms( view_kind: LanguageModelProviderTosView, accept_terms_of_service_in_progress: bool, accept_terms_callback: impl Fn(&mut Window, &mut App) + 'static, + app: &App, ) -> impl IntoElement { let thread_fresh_start = matches!(view_kind, LanguageModelProviderTosView::ThreadFreshStart); 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) .icon(IconName::ArrowUpRight) .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")); 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| { this.full_width() .style(ButtonStyle::Tinted(TintColor::Accent)) @@ -1135,19 +1141,19 @@ impl RenderOnce for ZedAiConfiguration { }; let manage_subscription_buttons = if is_pro { - Button::new("manage_settings", "Manage Subscription") + Button::new("manage_settings", "Manage Subscription", _cx) .full_width() .style(ButtonStyle::Tinted(TintColor::Accent)) .on_click(|_, _, cx| cx.open_url(&zed_urls::account_url(cx))) .into_any_element() } 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() .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent)) .on_click(|_, _, cx| cx.open_url(&zed_urls::start_trial_url(cx))) .into_any_element() } else { - Button::new("upgrade", "Upgrade to Pro") + Button::new("upgrade", "Upgrade to Pro", _cx) .full_width() .style(ui::ButtonStyle::Tinted(ui::TintColor::Accent)) .on_click(|_, _, cx| cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx))) @@ -1159,7 +1165,7 @@ impl RenderOnce for ZedAiConfiguration { .gap_2() .child(Label::new("Sign in to have access to Zed's complete agentic experience with hosted models.")) .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(IconName::Github) .icon_size(IconSize::Small) @@ -1183,12 +1189,13 @@ impl RenderOnce for ZedAiConfiguration { let callback = self.accept_terms_of_service_callback.clone(); move |window, cx| (callback)(window, cx) }, + _cx, )) }) .map(|this| { if self.has_accepted_terms_of_service && self.account_too_young { 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)) .full_width() .on_click(|_, _, cx| { diff --git a/crates/language_models/src/provider/copilot_chat.rs b/crates/language_models/src/provider/copilot_chat.rs index 73f73a9a31..d412415636 100644 --- a/crates/language_models/src/provider/copilot_chat.rs +++ b/crates/language_models/src/provider/copilot_chat.rs @@ -670,7 +670,7 @@ impl Render for ConfigurationView { .child(Label::new("Authorized")), ) .child( - Button::new("sign_out", "Sign Out") + Button::new("sign_out", "Sign Out", cx) .label_size(LabelSize::Small) .on_click(|_, window, 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."; 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(IconName::Github) .icon_position(IconPosition::Start) diff --git a/crates/language_models/src/provider/deepseek.rs b/crates/language_models/src/provider/deepseek.rs index a568ef4034..a0cd289556 100644 --- a/crates/language_models/src/provider/deepseek.rs +++ b/crates/language_models/src/provider/deepseek.rs @@ -679,11 +679,13 @@ impl Render for ConfigurationView { .child( List::new() .child(InstructionListItem::new( + cx, "Get your API key from the", Some("DeepSeek console"), Some("https://platform.deepseek.com/api_keys"), )) .child(InstructionListItem::text_only( + cx, "Paste your API key below and hit enter to start using the assistant", )), ) @@ -728,7 +730,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-key", "Reset Key") + Button::new("reset-key", "Reset Key", cx) .label_size(LabelSize::Small) .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) diff --git a/crates/language_models/src/provider/google.rs b/crates/language_models/src/provider/google.rs index b287e8181a..f0b86e1033 100644 --- a/crates/language_models/src/provider/google.rs +++ b/crates/language_models/src/provider/google.rs @@ -884,11 +884,13 @@ impl Render for ConfigurationView { .child( List::new() .child(InstructionListItem::new( + cx, "Create one by visiting", Some("Google AI's console"), Some("https://aistudio.google.com/app/apikey"), )) .child(InstructionListItem::text_only( + cx, "Paste your API key below and hit enter to start using the assistant", )), ) @@ -931,7 +933,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-key", "Reset Key") + Button::new("reset-key", "Reset Key", cx) .label_size(LabelSize::Small) .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) diff --git a/crates/language_models/src/provider/lmstudio.rs b/crates/language_models/src/provider/lmstudio.rs index 36a32ab941..25c64792f6 100644 --- a/crates/language_models/src/provider/lmstudio.rs +++ b/crates/language_models/src/provider/lmstudio.rs @@ -668,9 +668,11 @@ impl Render for ConfigurationView { v_flex().gap_1().child(Label::new(lmstudio_intro)).child( List::new() .child(InstructionListItem::text_only( + cx, "LM Studio needs to be running with at least one model downloaded.", )) .child(InstructionListItem::text_only( + cx, "To get your first model, try running `lms get qwen2.5-coder-7b`", )), ), @@ -687,7 +689,7 @@ impl Render for ConfigurationView { .map(|this| { if is_authenticated { this.child( - Button::new("lmstudio-site", "LM Studio") + Button::new("lmstudio-site", "LM Studio", cx) .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) @@ -702,6 +704,7 @@ impl Render for ConfigurationView { Button::new( "download_lmstudio_button", "Download LM Studio", + cx, ) .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) @@ -715,7 +718,7 @@ impl Render for ConfigurationView { } }) .child( - Button::new("view-models", "Model Catalog") + Button::new("view-models", "Model Catalog", cx) .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) @@ -741,7 +744,7 @@ impl Render for ConfigurationView { ) } else { this.child( - Button::new("retry_lmstudio_models", "Connect") + Button::new("retry_lmstudio_models", "Connect", cx) .icon_position(IconPosition::Start) .icon_size(IconSize::XSmall) .icon(IconName::PlayFilled) diff --git a/crates/language_models/src/provider/mistral.rs b/crates/language_models/src/provider/mistral.rs index 4a0d740334..b9fd7a8c19 100644 --- a/crates/language_models/src/provider/mistral.rs +++ b/crates/language_models/src/provider/mistral.rs @@ -848,14 +848,17 @@ impl Render for ConfigurationView { .child( List::new() .child(InstructionListItem::new( + cx, "Create one by visiting", Some("Mistral's console"), Some("https://console.mistral.ai/api-keys"), )) .child(InstructionListItem::text_only( + cx, "Ensure your Mistral account has credits", )) .child(InstructionListItem::text_only( + cx, "Paste your API key below and hit enter to start using the assistant", )), ) @@ -898,7 +901,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-key", "Reset Key") + Button::new("reset-key", "Reset Key", cx) .label_size(LabelSize::Small) .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) diff --git a/crates/language_models/src/provider/ollama.rs b/crates/language_models/src/provider/ollama.rs index 0c2b1107b1..9818e1a739 100644 --- a/crates/language_models/src/provider/ollama.rs +++ b/crates/language_models/src/provider/ollama.rs @@ -587,8 +587,9 @@ impl Render for ConfigurationView { .child( v_flex().gap_1().child(Label::new(ollama_intro)).child( 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( + cx, "Once installed, try `ollama run llama3.2`", )), ), @@ -605,7 +606,7 @@ impl Render for ConfigurationView { .map(|this| { if is_authenticated { this.child( - Button::new("ollama-site", "Ollama") + Button::new("ollama-site", "Ollama", cx) .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) @@ -618,6 +619,7 @@ impl Render for ConfigurationView { Button::new( "download_ollama_button", "Download Ollama", + cx, ) .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) @@ -631,7 +633,7 @@ impl Render for ConfigurationView { } }) .child( - Button::new("view-models", "View All Models") + Button::new("view-models", "View All Models", cx) .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) @@ -655,7 +657,7 @@ impl Render for ConfigurationView { ) } else { this.child( - Button::new("retry_ollama_models", "Connect") + Button::new("retry_ollama_models", "Connect", cx) .icon_position(IconPosition::Start) .icon_size(IconSize::XSmall) .icon(IconName::PlayFilled) diff --git a/crates/language_models/src/provider/open_ai.rs b/crates/language_models/src/provider/open_ai.rs index 725027b2a7..b5cc7aaf9c 100644 --- a/crates/language_models/src/provider/open_ai.rs +++ b/crates/language_models/src/provider/open_ai.rs @@ -812,14 +812,17 @@ impl Render for ConfigurationView { .child( List::new() .child(InstructionListItem::new( + cx, "Create one by visiting", Some("OpenAI's console"), Some("https://platform.openai.com/api-keys"), )) .child(InstructionListItem::text_only( + cx, "Ensure your OpenAI account has credits", )) .child(InstructionListItem::text_only( + cx, "Paste your API key below and hit enter to start using the assistant", )), ) @@ -857,7 +860,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-api-key", "Reset API Key") + Button::new("reset-api-key", "Reset API Key",cx) .label_size(LabelSize::Small) .icon(IconName::Undo) .icon_size(IconSize::Small) @@ -891,7 +894,7 @@ impl Render for ConfigurationView { .child(Label::new("Zed also supports OpenAI-compatible models.")), ) .child( - Button::new("docs", "Learn More") + Button::new("docs", "Learn More", cx) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) .icon_color(Color::Muted) diff --git a/crates/language_models/src/provider/open_ai_compatible.rs b/crates/language_models/src/provider/open_ai_compatible.rs index 6e912765cd..b75d32f44d 100644 --- a/crates/language_models/src/provider/open_ai_compatible.rs +++ b/crates/language_models/src/provider/open_ai_compatible.rs @@ -505,7 +505,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-api-key", "Reset API Key") + Button::new("reset-api-key", "Reset API Key",cx) .label_size(LabelSize::Small) .icon(IconName::Undo) .icon_size(IconSize::Small) diff --git a/crates/language_models/src/provider/open_router.rs b/crates/language_models/src/provider/open_router.rs index 3a492086f1..7bdbef57a3 100644 --- a/crates/language_models/src/provider/open_router.rs +++ b/crates/language_models/src/provider/open_router.rs @@ -859,14 +859,17 @@ impl Render for ConfigurationView { .child( List::new() .child(InstructionListItem::new( + cx, "Create an API key by visiting", Some("OpenRouter's console"), Some("https://openrouter.ai/keys"), )) .child(InstructionListItem::text_only( + cx, "Ensure your OpenRouter account has credits", )) .child(InstructionListItem::text_only( + cx, "Paste your API key below and hit enter to start using the assistant", )), ) @@ -909,7 +912,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-key", "Reset Key") + Button::new("reset-key", "Reset Key",cx) .label_size(LabelSize::Small) .icon(Some(IconName::Trash)) .icon_size(IconSize::Small) diff --git a/crates/language_models/src/provider/vercel.rs b/crates/language_models/src/provider/vercel.rs index 57a89ba4aa..45da1f5899 100644 --- a/crates/language_models/src/provider/vercel.rs +++ b/crates/language_models/src/provider/vercel.rs @@ -517,11 +517,13 @@ impl Render for ConfigurationView { .child( List::new() .child(InstructionListItem::new( + cx, "Create one by visiting", Some("Vercel v0's console"), Some("https://v0.dev/chat/settings/keys"), )) .child(InstructionListItem::text_only( + cx, "Paste your API key below and hit enter to start using the agent", )), ) @@ -559,7 +561,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-api-key", "Reset API Key") + Button::new("reset-api-key", "Reset API Key",cx) .label_size(LabelSize::Small) .icon(IconName::Undo) .icon_size(IconSize::Small) diff --git a/crates/language_models/src/provider/x_ai.rs b/crates/language_models/src/provider/x_ai.rs index 5e7190ea96..72cfea90ea 100644 --- a/crates/language_models/src/provider/x_ai.rs +++ b/crates/language_models/src/provider/x_ai.rs @@ -507,11 +507,13 @@ impl Render for ConfigurationView { .child( List::new() .child(InstructionListItem::new( + cx, "Create one by visiting", Some("xAI console"), Some("https://console.x.ai/team/default/api-keys"), )) .child(InstructionListItem::text_only( + cx, "Paste your API key below and hit enter to start using the agent", )), ) @@ -549,7 +551,7 @@ impl Render for ConfigurationView { })), ) .child( - Button::new("reset-api-key", "Reset API Key") + Button::new("reset-api-key", "Reset API Key",cx) .label_size(LabelSize::Small) .icon(IconName::Undo) .icon_size(IconSize::Small) diff --git a/crates/language_models/src/ui/instruction_list_item.rs b/crates/language_models/src/ui/instruction_list_item.rs index 3dee97aff6..6bb748a72a 100644 --- a/crates/language_models/src/ui/instruction_list_item.rs +++ b/crates/language_models/src/ui/instruction_list_item.rs @@ -2,27 +2,31 @@ use gpui::{AnyElement, IntoElement, ParentElement, SharedString}; use ui::{ListItem, prelude::*}; /// A reusable list item component for adding LLM provider configuration instructions -pub struct InstructionListItem { +pub struct InstructionListItem<'app> { + app: &'app App, label: SharedString, button_label: Option, button_link: Option, } -impl InstructionListItem { +impl<'app> InstructionListItem<'app> { pub fn new( + app: &'app App, label: impl Into, button_label: Option>, button_link: Option>, ) -> Self { Self { + app, label: label.into(), button_label: button_label.map(|l| l.into()), button_link: button_link.map(|l| l.into()), } } - pub fn text_only(label: impl Into) -> Self { + pub fn text_only(app: &'app App, label: impl Into) -> Self { Self { + app, label: label.into(), button_label: None, button_link: None, @@ -30,7 +34,7 @@ impl InstructionListItem { } } -impl IntoElement for InstructionListItem { +impl IntoElement for InstructionListItem<'_> { type Element = AnyElement; fn into_element(self) -> Self::Element { @@ -44,7 +48,7 @@ impl IntoElement for InstructionListItem { .flex_wrap() .child(Label::new(self.label)) .child( - Button::new(unique_id, button_label) + Button::new(unique_id, button_label, self.app) .style(ButtonStyle::Subtle) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) diff --git a/crates/language_selector/src/active_buffer_language.rs b/crates/language_selector/src/active_buffer_language.rs index c5c5eceab5..0e1a04255c 100644 --- a/crates/language_selector/src/active_buffer_language.rs +++ b/crates/language_selector/src/active_buffer_language.rs @@ -55,7 +55,7 @@ impl Render for ActiveBufferLanguage { }; el.child( - Button::new("change-language", active_language_text) + Button::new("change-language", active_language_text, cx) .label_size(LabelSize::Small) .on_click(cx.listener(|this, _, window, cx| { if let Some(workspace) = this.workspace.upgrade() { diff --git a/crates/language_tools/src/key_context_view.rs b/crates/language_tools/src/key_context_view.rs index 88131781ec..2a1c6116a5 100644 --- a/crates/language_tools/src/key_context_view.rs +++ b/crates/language_tools/src/key_context_view.rs @@ -206,12 +206,12 @@ impl Render for KeyContextView { .mt_4() .gap_4() .child( - Button::new("open_documentation", "Open Documentation") + Button::new("open_documentation", "Open Documentation", cx) .style(ButtonStyle::Filled) .on_click(|_, _, cx| cx.open_url("https://zed.dev/docs/key-bindings")), ) .child( - Button::new("view_default_keymap", "View default keymap") + Button::new("view_default_keymap", "View default keymap", cx) .style(ButtonStyle::Filled) .key_binding(ui::KeyBinding::for_action( &zed_actions::OpenDefaultKeymap, @@ -223,7 +223,7 @@ impl Render for KeyContextView { }), ) .child( - Button::new("edit_your_keymap", "Edit your keymap") + Button::new("edit_your_keymap", "Edit your keymap", cx) .style(ButtonStyle::Filled) .key_binding(ui::KeyBinding::for_action(&zed_actions::OpenKeymap, window, cx)) .on_click(|_, window, cx| { diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 606f3a3f0e..da21db0b27 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -1374,6 +1374,7 @@ impl Render for LspLogToolbarItemView { )) }) .unwrap_or_else(|| "No server selected".into()), + cx, ) .icon(IconName::ChevronDown) .icon_size(IconSize::Small) @@ -1431,10 +1432,14 @@ impl Render for LspLogToolbarItemView { PopoverMenu::new("LspViewSelector") .anchor(Corner::TopLeft) .trigger( - Button::new("language_server_menu_header", server.selected_entry.label()) - .icon(IconName::ChevronDown) - .icon_size(IconSize::Small) - .icon_color(Color::Muted), + Button::new( + "language_server_menu_header", + server.selected_entry.label(), + cx, + ) + .icon(IconName::ChevronDown) + .icon_size(IconSize::Small) + .icon_color(Color::Muted), ) .menu(move |window, cx| { let log_toolbar_view = log_toolbar_view.clone(); @@ -1516,22 +1521,26 @@ impl Render for LspLogToolbarItemView { .gap_0p5() .child(lsp_menu) .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 { LogKind::Trace => { let log_view = log_view.clone(); div().child( PopoverMenu::new("lsp-trace-level-menu") .anchor(Corner::TopLeft) - .trigger( - Button::new( - "language_server_trace_level_selector", - "Trace level", - ) - .icon(IconName::ChevronDown) - .icon_size(IconSize::Small) - .icon_color(Color::Muted), - ) + .trigger(trace_button) .menu({ let log_view = log_view.clone(); @@ -1591,15 +1600,7 @@ impl Render for LspLogToolbarItemView { div().child( PopoverMenu::new("lsp-log-level-menu") .anchor(Corner::TopLeft) - .trigger( - Button::new( - "language_server_log_level_selector", - "Log level", - ) - .icon(IconName::ChevronDown) - .icon_size(IconSize::Small) - .icon_color(Color::Muted), - ) + .trigger(log_button) .menu({ let log_view = log_view.clone(); @@ -1656,11 +1657,11 @@ impl Render for LspLogToolbarItemView { ) } _ => div(), - }), - ), + }) + }), ) .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| { if let Some(log_view) = this.log_view.as_ref() { log_view.update(cx, |log_view, cx| { diff --git a/crates/notifications/src/status_toast.rs b/crates/notifications/src/status_toast.rs index 7affa93f5a..7c18b12d9c 100644 --- a/crates/notifications/src/status_toast.rs +++ b/crates/notifications/src/status_toast.rs @@ -122,7 +122,7 @@ impl Render for StatusToast { .child(Label::new(self.text.clone()).color(Color::Default)) .when_some(self.action.as_ref(), |this, action| { this.child( - Button::new(action.id.clone(), action.label.clone()) + Button::new(action.id.clone(), action.label.clone(), cx) .tooltip(Tooltip::for_action_title( action.label.clone(), &toast::RunAction, diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs index 8203f96479..6f8249a231 100644 --- a/crates/onboarding/src/ai_setup_page.rs +++ b/crates/onboarding/src/ai_setup_page.rs @@ -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()), ) .child( - Button::new("learn_more", "Learn More") + Button::new("learn_more", "Learn More", cx) .style(ButtonStyle::Outlined) .label_size(LabelSize::Small) .icon(IconName::ArrowUpRight) @@ -206,7 +206,7 @@ fn render_llm_provider_card( )) .child(Divider::horizontal()) .child( - Button::new("agent_settings", "Add Many Others") + Button::new("agent_settings", "Add Many Others", cx) .size(ButtonSize::Large) .icon(IconName::Plus) .icon_position(IconPosition::Start) @@ -363,7 +363,7 @@ impl Render for AiConfigurationModal { .section(Section::new().child(self.configuration_view.clone())) .footer( ModalFooter::new().end_slot( - Button::new("ai-onb-modal-Done", "Done") + Button::new("ai-onb-modal-Done", "Done", cx) .key_binding( KeyBinding::for_action_in( &menu::Cancel, diff --git a/crates/onboarding/src/onboarding.rs b/crates/onboarding/src/onboarding.rs index 7ba7ba60cb..01b3af0d15 100644 --- a/crates/onboarding/src/onboarding.rs +++ b/crates/onboarding/src/onboarding.rs @@ -458,7 +458,7 @@ impl Onboarding { ) .into_any_element() } else { - Button::new("sign_in", "Sign In") + Button::new("sign_in", "Sign In", cx) .full_width() .style(ButtonStyle::Outlined) .size(ButtonSize::Medium) diff --git a/crates/onboarding/src/welcome.rs b/crates/onboarding/src/welcome.rs index 65baad03a0..874560e744 100644 --- a/crates/onboarding/src/welcome.rs +++ b/crates/onboarding/src/welcome.rs @@ -235,7 +235,7 @@ impl Render for WelcomePage { .border_color(cx.theme().colors().border.opacity(0.6)) .border_dashed() .child( - Button::new("welcome-exit", "Return to Setup") + Button::new("welcome-exit", "Return to Setup", cx) .tab_index(last_index as isize) .full_width() .label_size(LabelSize::XSmall) diff --git a/crates/panel/src/panel.rs b/crates/panel/src/panel.rs index 658a51167b..2bf2a84f4c 100644 --- a/crates/panel/src/panel.rs +++ b/crates/panel/src/panel.rs @@ -50,10 +50,10 @@ impl RenderOnce for PanelTab { } } -pub fn panel_button(label: impl Into) -> ui::Button { +pub fn panel_button(label: impl Into, app: &App) -> ui::Button { let label = label.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) .icon_size(ui::IconSize::Small) // TODO: Change this once we use on_surface_bg in button_like @@ -61,8 +61,8 @@ pub fn panel_button(label: impl Into) -> ui::Button { .size(ui::ButtonSize::Compact) } -pub fn panel_filled_button(label: impl Into) -> ui::Button { - panel_button(label).style(ui::ButtonStyle::Filled) +pub fn panel_filled_button(label: impl Into, app: &App) -> ui::Button { + panel_button(label, app).style(ui::ButtonStyle::Filled) } pub fn panel_icon_button(id: impl Into, icon: IconName) -> ui::IconButton { diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 967df41e23..9158fa5aa1 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5531,7 +5531,7 @@ impl Render for ProjectPanel { .p_4() .track_focus(&self.focus_handle(cx)) .child( - Button::new("open_project", "Open a project") + Button::new("open_project", "Open a project", cx) .full_width() .key_binding(KeyBinding::for_action_in( &OpenRecent::default(), diff --git a/crates/recent_projects/src/disconnected_overlay.rs b/crates/recent_projects/src/disconnected_overlay.rs index a6cd26355c..32061eb6dc 100644 --- a/crates/recent_projects/src/disconnected_overlay.rs +++ b/crates/recent_projects/src/disconnected_overlay.rs @@ -185,7 +185,7 @@ impl Render for DisconnectedOverlay { h_flex() .gap_2() .child( - Button::new("close-window", "Close Window") + Button::new("close-window", "Close Window", cx) .style(ButtonStyle::Filled) .layer(ElevationIndex::ModalSurface) .on_click(cx.listener(move |_, _, window, _| { @@ -194,7 +194,7 @@ impl Render for DisconnectedOverlay { ) .when(can_reconnect, |el| { el.child( - Button::new("reconnect", "Reconnect") + Button::new("reconnect", "Reconnect", cx) .style(ButtonStyle::Filled) .layer(ElevationIndex::ModalSurface) .icon(IconName::ArrowCircle) diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 2093e96cae..fff2259f43 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -479,7 +479,7 @@ impl PickerDelegate for RecentProjectsDelegate { .border_t_1() .border_color(cx.theme().colors().border_variant) .child( - Button::new("remote", "Open Remote Folder") + Button::new("remote", "Open Remote Folder", cx) .key_binding(KeyBinding::for_action( &OpenRemote { from_existing_connection: false, @@ -500,7 +500,7 @@ impl PickerDelegate for RecentProjectsDelegate { }), ) .child( - Button::new("local", "Open Local Folder") + Button::new("local", "Open Local Folder", cx) .key_binding(KeyBinding::for_action(&workspace::Open, window, cx)) .on_click(|_, window, cx| { window.dispatch_action(workspace::Open.boxed_clone(), cx) diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 354434a7fc..787dea911c 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -1094,7 +1094,7 @@ impl RemoteServerProjects { .size(LabelSize::Small), ) .child( - Button::new("learn-more", "Learn more…") + Button::new("learn-more", "Learn more…", cx) .label_size(LabelSize::Small) .size(ButtonSize::None) .color(Color::Accent) diff --git a/crates/repl/src/components/kernel_options.rs b/crates/repl/src/components/kernel_options.rs index cd73783b4c..0d92e2dc55 100644 --- a/crates/repl/src/components/kernel_options.rs +++ b/crates/repl/src/components/kernel_options.rs @@ -234,7 +234,7 @@ impl PickerDelegate for KernelPickerDelegate { .p_1() .gap_4() .child( - Button::new("kernel-docs", "Kernel Docs") + Button::new("kernel-docs", "Kernel Docs", cx) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) .icon_color(Color::Muted) diff --git a/crates/repl/src/session.rs b/crates/repl/src/session.rs index 729a616135..b98b75b329 100644 --- a/crates/repl/src/session.rs +++ b/crates/repl/src/session.rs @@ -655,7 +655,7 @@ impl Render for Session { .as_ref() .map(|info| info.language_info.name.clone()), Some( - Button::new("interrupt", "Interrupt") + Button::new("interrupt", "Interrupt", cx) .style(ButtonStyle::Subtle) .on_click(cx.listener(move |session, _, _, cx| { session.interrupt(cx); @@ -684,7 +684,7 @@ impl Render for Session { .child(Label::new(self.kernel_specification.name())) .children(status_text.map(|status_text| Label::new(format!("({status_text})")))) .button( - Button::new("shutdown", "Shutdown") + Button::new("shutdown", "Shutdown", cx) .style(ButtonStyle::Subtle) .disabled(self.kernel.is_shutting_down()) .on_click(cx.listener(move |session, _, window, cx| { diff --git a/crates/rules_library/src/rules_library.rs b/crates/rules_library/src/rules_library.rs index ebec96dd7b..edbd5acb3e 100644 --- a/crates/rules_library/src/rules_library.rs +++ b/crates/rules_library/src/rules_library.rs @@ -1318,19 +1318,21 @@ impl Render for RulesLibrary { "Create your first rule:", )) .child( - Button::new("create-rule", "New Rule") - .full_width() - .key_binding( - KeyBinding::for_action( - &NewRule, window, cx, - ), + Button::new( + "create-rule", + "New Rule", + 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()), diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 69beeec3be..c8d5909a77 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -1439,7 +1439,7 @@ impl ProjectSearchView { .mb_2(), ) .child( - Button::new("filter-paths", "Include/exclude specific paths") + Button::new("filter-paths", "Include/exclude specific paths", cx) .icon(IconName::Filter) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) @@ -1454,7 +1454,7 @@ impl ProjectSearchView { }), ) .child( - Button::new("find-replace", "Find and replace") + Button::new("find-replace", "Find and replace", cx) .icon(IconName::Replace) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) @@ -1469,7 +1469,7 @@ impl ProjectSearchView { }), ) .child( - Button::new("regex", "Match with regex") + Button::new("regex", "Match with regex", cx) .icon(IconName::Regex) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) @@ -1484,7 +1484,7 @@ impl ProjectSearchView { }), ) .child( - Button::new("match-case", "Match case") + Button::new("match-case", "Match case", cx) .icon(IconName::CaseSensitive) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) @@ -1499,7 +1499,7 @@ impl ProjectSearchView { }), ) .child( - Button::new("match-whole-words", "Match whole words") + Button::new("match-whole-words", "Match whole words", cx) .icon(IconName::WholeWord) .icon_position(IconPosition::Start) .icon_size(IconSize::Small) diff --git a/crates/semantic_index/src/project_index_debug_view.rs b/crates/semantic_index/src/project_index_debug_view.rs index 8d6a49c45c..60a3cd4ddf 100644 --- a/crates/semantic_index/src/project_index_debug_view.rs +++ b/crates/semantic_index/src/project_index_debug_view.rs @@ -135,6 +135,15 @@ impl ProjectIndexDebugView { let colors = cx.theme().colors(); 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() .text_ui(cx) @@ -146,26 +155,10 @@ impl ProjectIndexDebugView { .child(format!( "chunk {} of {}. length: {}", ix + 1, - state.chunks.len(), + chunk_count, chunk.len(), )) - .child( - 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(h_flex().child(prev_button).child(next_button)), ) .child( div() diff --git a/crates/settings_ui/src/keybindings.rs b/crates/settings_ui/src/keybindings.rs index a62c669488..b4a23f74ed 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/settings_ui/src/keybindings.rs @@ -2469,7 +2469,7 @@ impl Render for KeybindingEditorModal { .color(Color::Muted), ) .child( - Button::new("show_matching", "View") + Button::new("show_matching", "View", cx) .label_size(LabelSize::Small) .icon(IconName::ArrowUpRight) .icon_color(Color::Muted) @@ -2508,10 +2508,10 @@ impl Render for KeybindingEditorModal { h_flex() .gap_1() .child( - Button::new("cancel", "Cancel") + Button::new("cancel", "Cancel", cx) .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.save_or_display_error(cx); }, diff --git a/crates/settings_ui/src/ui_components/table.rs b/crates/settings_ui/src/ui_components/table.rs index 2b3e815f36..30b75a83f8 100644 --- a/crates/settings_ui/src/ui_components/table.rs +++ b/crates/settings_ui/src/ui_components/table.rs @@ -1410,7 +1410,7 @@ impl Component for Table<3> { "Project A".into_any_element(), "High".into_any_element(), "2023-12-31".into_any_element(), - Button::new("view_a", "View") + Button::new("view_a", "View", _cx) .style(ButtonStyle::Filled) .full_width() .into_any_element(), @@ -1420,7 +1420,7 @@ impl Component for Table<3> { "Project B".into_any_element(), "Medium".into_any_element(), "2024-03-15".into_any_element(), - Button::new("view_b", "View") + Button::new("view_b", "View", _cx) .style(ButtonStyle::Filled) .full_width() .into_any_element(), @@ -1430,7 +1430,7 @@ impl Component for Table<3> { "Project C".into_any_element(), "Low".into_any_element(), "2024-06-30".into_any_element(), - Button::new("view_c", "View") + Button::new("view_c", "View", _cx) .style(ButtonStyle::Filled) .full_width() .into_any_element(), diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index c4b0931c35..50b0027b0a 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -668,7 +668,7 @@ impl PickerDelegate for TasksModalDelegate { .map(|(label, action)| { 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)) .on_click(move |_, window, cx| { window.dispatch_action(action.boxed_clone(), cx); @@ -691,7 +691,7 @@ impl PickerDelegate for TasksModalDelegate { "Spawn Oneshot" }; - Button::new("spawn-onehshot", spawn_oneshot_label) + Button::new("spawn-onehshot", spawn_oneshot_label, cx) .key_binding(keybind) .on_click(move |_, window, cx| { window.dispatch_action(action.boxed_clone(), cx) @@ -706,14 +706,14 @@ impl PickerDelegate for TasksModalDelegate { } else { "Spawn Without History" }; - Button::new("spawn", label).key_binding(keybind).on_click( - move |_, window, cx| { + Button::new("spawn", label, cx) + .key_binding(keybind) + .on_click(move |_, window, cx| { window.dispatch_action( menu::SecondaryConfirm.boxed_clone(), cx, ) - }, - ) + }) }, ), ) @@ -723,7 +723,7 @@ impl PickerDelegate for TasksModalDelegate { let run_entry_label = if is_recent_selected { "Rerun" } else { "Spawn" }; - Button::new("spawn", run_entry_label) + Button::new("spawn", run_entry_label, cx) .key_binding(keybind) .on_click(|_, window, cx| { window.dispatch_action(menu::Confirm.boxed_clone(), cx); diff --git a/crates/theme_selector/src/icon_theme_selector.rs b/crates/theme_selector/src/icon_theme_selector.rs index af7abdee62..e6892f7932 100644 --- a/crates/theme_selector/src/icon_theme_selector.rs +++ b/crates/theme_selector/src/icon_theme_selector.rs @@ -315,7 +315,7 @@ impl PickerDelegate for IconThemeSelectorDelegate { .border_t_1() .border_color(cx.theme().colors().border_variant) .child( - Button::new("docs", "View Icon Theme Docs") + Button::new("docs", "View Icon Theme Docs", cx) .icon(IconName::ArrowUpRight) .icon_position(IconPosition::End) .icon_size(IconSize::Small) @@ -325,7 +325,7 @@ impl PickerDelegate for IconThemeSelectorDelegate { }), ) .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| { window.dispatch_action( Box::new(Extensions { diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 8c48f295dd..490008b2d0 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -373,7 +373,7 @@ impl PickerDelegate for ThemeSelectorDelegate { .border_t_1() .border_color(cx.theme().colors().border_variant) .child( - Button::new("docs", "View Theme Docs") + Button::new("docs", "View Theme Docs", cx) .icon(IconName::ArrowUpRight) .icon_position(IconPosition::End) .icon_size(IconSize::Small) @@ -383,7 +383,7 @@ impl PickerDelegate for ThemeSelectorDelegate { })), ) .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| { window.dispatch_action( Box::new(Extensions { diff --git a/crates/title_bar/src/application_menu.rs b/crates/title_bar/src/application_menu.rs index 98f0eeb6cc..befa190985 100644 --- a/crates/title_bar/src/application_menu.rs +++ b/crates/title_bar/src/application_menu.rs @@ -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 menu_name = entry.menu.name.clone(); @@ -187,6 +187,7 @@ impl ApplicationMenu { Button::new( SharedString::from(format!("{}-menu-trigger", menu_name)), menu_name.clone(), + cx, ) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small), @@ -310,7 +311,7 @@ impl Render for ApplicationMenu { this.children( self.entries .iter() - .map(|entry| self.render_standard_menu(entry)), + .map(|entry| self.render_standard_menu(entry, cx)), ) }) } diff --git a/crates/title_bar/src/collab.rs b/crates/title_bar/src/collab.rs index 74d60a6d66..9d4d1c79ee 100644 --- a/crates/title_bar/src/collab.rs +++ b/crates/title_bar/src/collab.rs @@ -366,6 +366,7 @@ impl TitleBar { Button::new( "toggle_sharing", if is_shared { "Unshare" } else { "Share" }, + cx, ) .tooltip(Tooltip::text(if is_shared { "Stop sharing project with call participants" diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index eb317a5616..c3946c0e89 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -390,7 +390,7 @@ impl TitleBar { if self.project.read(cx).is_disconnected(cx) { return Some( - Button::new("disconnected", "Disconnected") + Button::new("disconnected", "Disconnected", cx) .disabled(true) .color(Color::Disabled) .style(ButtonStyle::Subtle) @@ -407,7 +407,7 @@ impl TitleBar { .participant_indices() .get(&host_user.id)?; 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)) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) @@ -445,7 +445,7 @@ impl TitleBar { "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)) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) @@ -491,7 +491,7 @@ impl TitleBar { }?; Some( - Button::new("project_branch_trigger", branch_name) + Button::new("project_branch_trigger", branch_name, cx) .color(Color::Muted) .style(ButtonStyle::Subtle) .label_size(LabelSize::Small) @@ -590,7 +590,7 @@ impl TitleBar { }; Some( - Button::new("connection-status", label) + Button::new("connection-status", label, cx) .label_size(LabelSize::Small) .on_click(|_, window, 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) -> Button { + pub fn render_sign_in_button(&mut self, cx: &mut Context) -> Button { let client = self.client.clone(); - Button::new("sign_in", "Sign in") + Button::new("sign_in", "Sign in", cx) .label_size(LabelSize::Small) .on_click(move |_, window, cx| { let client = client.clone(); diff --git a/crates/toolchain_selector/src/active_toolchain.rs b/crates/toolchain_selector/src/active_toolchain.rs index 01bd7b0a9c..575efbae73 100644 --- a/crates/toolchain_selector/src/active_toolchain.rs +++ b/crates/toolchain_selector/src/active_toolchain.rs @@ -232,7 +232,7 @@ impl Render for ActiveToolchain { div().when_some(self.active_toolchain.as_ref(), |el, active_toolchain| { let term = self.term.clone(); el.child( - Button::new("change-toolchain", active_toolchain.name.clone()) + Button::new("change-toolchain", active_toolchain.name.clone(), cx) .label_size(LabelSize::Small) .on_click(cx.listener(|this, _, window, cx| { if let Some(workspace) = this.workspace.upgrade() { diff --git a/crates/ui/src/components/banner.rs b/crates/ui/src/components/banner.rs index d493e8a0d3..f843bbfdfa 100644 --- a/crates/ui/src/components/banner.rs +++ b/crates/ui/src/components/banner.rs @@ -134,7 +134,7 @@ impl Component for Banner { ComponentScope::DataDisplay } - fn preview(_window: &mut Window, _cx: &mut App) -> Option { + fn preview(_window: &mut Window, cx: &mut App) -> Option { let severity_examples = vec![ single_example( "Default", @@ -148,7 +148,7 @@ impl Component for Banner { .severity(Severity::Info) .child(Label::new("This is an informational message")) .action_slot( - Button::new("learn-more", "Learn More") + Button::new("learn-more", "Learn More", cx) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) .icon_position(IconPosition::End), @@ -160,7 +160,7 @@ impl Component for Banner { Banner::new() .severity(Severity::Success) .child(Label::new("Operation completed successfully")) - .action_slot(Button::new("dismiss", "Dismiss")) + .action_slot(Button::new("dismiss", "Dismiss", cx)) .into_any_element(), ), single_example( @@ -168,7 +168,7 @@ impl Component for Banner { Banner::new() .severity(Severity::Warning) .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(), ), single_example( @@ -176,7 +176,7 @@ impl Component for Banner { Banner::new() .severity(Severity::Error) .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(), ), ]; diff --git a/crates/ui/src/components/button/button.rs b/crates/ui/src/components/button/button.rs index 19f782fb98..dfc2a4019b 100644 --- a/crates/ui/src/components/button/button.rs +++ b/crates/ui/src/components/button/button.rs @@ -1,5 +1,5 @@ use crate::component_prelude::*; -use gpui::{AnyElement, AnyView, DefiniteLength}; +use gpui::{AnyElement, AnyView, DefiniteLength, FocusHandle, Focusable}; use ui_macros::RegisterComponent; use crate::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label}; @@ -79,6 +79,7 @@ use super::button_icon::ButtonIcon; /// #[derive(IntoElement, Documented, RegisterComponent)] pub struct Button { + focus_handle: FocusHandle, base: ButtonLike, label: SharedString, label_color: Option, @@ -104,8 +105,9 @@ impl Button { /// the button with the provided identifier and label text, setting all other /// properties to their default values, which can be customized using the /// builder pattern methods provided by this struct. - pub fn new(id: impl Into, label: impl Into) -> Self { + pub fn new(id: impl Into, label: impl Into, app: &App) -> Self { Self { + focus_handle: app.focus_handle(), base: ButtonLike::new(id), label: label.into(), 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 { /// Sets the selected state of the button. /// @@ -483,7 +491,7 @@ impl Component for Button { Some("A button triggers an event or action.") } - fn preview(_window: &mut Window, _cx: &mut App) -> Option { + fn preview(_window: &mut Window, cx: &mut App) -> Option { Some( v_flex() .gap_6() @@ -493,29 +501,29 @@ impl Component for Button { vec![ single_example( "Default", - Button::new("default", "Default").into_any_element(), + Button::new("default", "Default", cx).into_any_element(), ), single_example( "Filled", - Button::new("filled", "Filled") + Button::new("filled", "Filled", cx) .style(ButtonStyle::Filled) .into_any_element(), ), single_example( "Subtle", - Button::new("outline", "Subtle") + Button::new("outlined", "Outlined", cx) .style(ButtonStyle::Subtle) .into_any_element(), ), single_example( "Tinted", - Button::new("tinted_accent_style", "Accent") + Button::new("tinted_accent_style", "Accent", cx) .style(ButtonStyle::Tinted(TintColor::Accent)) .into_any_element(), ), single_example( "Transparent", - Button::new("transparent", "Transparent") + Button::new("transparent", "Transparent", cx) .style(ButtonStyle::Transparent) .into_any_element(), ), @@ -526,25 +534,25 @@ impl Component for Button { vec![ single_example( "Accent", - Button::new("tinted_accent", "Accent") + Button::new("color_accent", "Accent", cx) .style(ButtonStyle::Tinted(TintColor::Accent)) .into_any_element(), ), single_example( "Error", - Button::new("tinted_negative", "Error") + Button::new("tinted_negative", "Error", cx) .style(ButtonStyle::Tinted(TintColor::Error)) .into_any_element(), ), single_example( "Warning", - Button::new("tinted_warning", "Warning") + Button::new("tinted_warning", "Warning", cx) .style(ButtonStyle::Tinted(TintColor::Warning)) .into_any_element(), ), single_example( "Success", - Button::new("tinted_positive", "Success") + Button::new("tinted_positive", "Success", cx) .style(ButtonStyle::Tinted(TintColor::Success)) .into_any_element(), ), @@ -555,17 +563,17 @@ impl Component for Button { vec![ single_example( "Default", - Button::new("default_state", "Default").into_any_element(), + Button::new("default_state", "Default", cx).into_any_element(), ), single_example( "Disabled", - Button::new("disabled", "Disabled") + Button::new("disabled", "Disabled", cx) .disabled(true) .into_any_element(), ), single_example( "Selected", - Button::new("selected", "Selected") + Button::new("selected", "Selected", cx) .toggle_state(true) .into_any_element(), ), @@ -576,21 +584,21 @@ impl Component for Button { vec![ single_example( "Icon Start", - Button::new("icon_start", "Icon Start") + Button::new("icon-start", "Icon Start", cx) .icon(IconName::Check) .icon_position(IconPosition::Start) .into_any_element(), ), single_example( "Icon End", - Button::new("icon_end", "Icon End") + Button::new("icon-end", "Icon End", cx) .icon(IconName::Check) .icon_position(IconPosition::End) .into_any_element(), ), single_example( "Icon Color", - Button::new("icon_color", "Icon Color") + Button::new("icon_color", "Icon Color", cx) .icon(IconName::Check) .icon_color(Color::Accent) .into_any_element(), diff --git a/crates/ui/src/components/callout.rs b/crates/ui/src/components/callout.rs index abb03198ab..91c4ffd9c1 100644 --- a/crates/ui/src/components/callout.rs +++ b/crates/ui/src/components/callout.rs @@ -167,7 +167,7 @@ impl Component for Callout { ) } - fn preview(_window: &mut Window, _cx: &mut App) -> Option { + fn preview(_window: &mut Window, cx: &mut App) -> Option { let callout_examples = vec![ single_example( "Simple with Title Only", @@ -178,7 +178,9 @@ impl Component for Callout { .size(IconSize::Small), ) .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(), ) .width(px(580.)), @@ -195,10 +197,10 @@ impl Component for Callout { "We'll backup your current settings and update them to the new format.", ) .primary_action( - Button::new("update", "Backup & Update").label_size(LabelSize::Small), + Button::new("update", "Backup & Update", cx).label_size(LabelSize::Small), ) .secondary_action( - Button::new("dismiss", "Dismiss").label_size(LabelSize::Small), + Button::new("dismiss", "Dismiss", cx).label_size(LabelSize::Small), ) .into_any_element(), ) @@ -214,10 +216,12 @@ impl Component for Callout { .title("Thread reached the token limit") .description("Start a new thread from a summary to continue the conversation.") .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( - Button::new("view-summary", "View Summary").label_size(LabelSize::Small), + Button::new("view-summary", "View Summary", cx) + .label_size(LabelSize::Small), ) .into_any_element(), ) @@ -233,10 +237,10 @@ impl Component for Callout { .title("Upgrade to Pro") .description("• Unlimited threads\n• Priority support\n• Advanced analytics") .primary_action( - Button::new("upgrade", "Upgrade Now").label_size(LabelSize::Small), + Button::new("upgrade", "Upgrade Now", cx).label_size(LabelSize::Small), ) .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(), ) diff --git a/crates/ui/src/components/context_menu.rs b/crates/ui/src/components/context_menu.rs index 25575c4f1e..17034a8a2f 100644 --- a/crates/ui/src/components/context_menu.rs +++ b/crates/ui/src/components/context_menu.rs @@ -811,7 +811,7 @@ impl ContextMenu { ListSubHeader::new(header.clone()) .inset(true) .end_slot( - Button::new(link_id, label.clone()) + Button::new(link_id, label.clone(), cx) .color(Color::Muted) .label_size(LabelSize::Small) .size(ButtonSize::None) diff --git a/crates/ui/src/components/notification/alert_modal.rs b/crates/ui/src/components/notification/alert_modal.rs index acba0c7e9a..d032fb7d52 100644 --- a/crates/ui/src/components/notification/alert_modal.rs +++ b/crates/ui/src/components/notification/alert_modal.rs @@ -59,12 +59,17 @@ impl RenderOnce for AlertModal { .items_center() .gap_1() .child( - Button::new(self.dismiss_label.clone(), self.dismiss_label.clone()) - .color(Color::Muted), + Button::new( + self.dismiss_label.clone(), + self.dismiss_label.clone(), + cx, + ) + .color(Color::Muted), ) .child(Button::new( self.primary_action.clone(), self.primary_action.clone(), + cx, )), ), ) diff --git a/crates/ui/src/components/settings_container.rs b/crates/ui/src/components/settings_container.rs index 31cb1b32f8..3790a13891 100644 --- a/crates/ui/src/components/settings_container.rs +++ b/crates/ui/src/components/settings_container.rs @@ -49,7 +49,7 @@ impl Component for SettingsContainer { Some("A container for organizing and displaying settings in a structured manner.") } - fn preview(_window: &mut Window, _cx: &mut App) -> Option { + fn preview(_window: &mut Window, cx: &mut App) -> Option { Some( v_flex() .gap_6() @@ -78,7 +78,7 @@ impl Component for SettingsContainer { SettingsContainer::new() .child(Label::new("Text Setting")) .child(Checkbox::new("checkbox", ToggleState::Unselected)) - .child(Button::new("button", "Click me")) + .child(Button::new("button", "Click me", cx)) .into_any_element(), )], ), diff --git a/crates/ui/src/components/tab_bar.rs b/crates/ui/src/components/tab_bar.rs index 3c467c06ce..f098a196dc 100644 --- a/crates/ui/src/components/tab_bar.rs +++ b/crates/ui/src/components/tab_bar.rs @@ -165,7 +165,7 @@ impl Component for TabBar { Some("A horizontal bar containing tabs for navigation between different views or sections.") } - fn preview(_window: &mut Window, _cx: &mut App) -> Option { + fn preview(_window: &mut Window, cx: &mut App) -> Option { Some( v_flex() .gap_6() @@ -192,11 +192,11 @@ impl Component for TabBar { vec![single_example( "Full TabBar", 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("tab2")) .child(Tab::new("tab3")) - .end_child(Button::new("end_button", "End")) + .end_child(Button::new("end_button", "End", cx)) .into_any_element(), )], ), diff --git a/crates/ui/src/components/tooltip.rs b/crates/ui/src/components/tooltip.rs index ed0fdd0114..c693f2aded 100644 --- a/crates/ui/src/components/tooltip.rs +++ b/crates/ui/src/components/tooltip.rs @@ -283,11 +283,11 @@ impl Component for Tooltip { ) } - fn preview(_window: &mut Window, _cx: &mut App) -> Option { + fn preview(_window: &mut Window, cx: &mut App) -> Option { Some( example_group(vec![single_example( "Text only", - Button::new("delete-example", "Delete") + Button::new("delete-example", "Delete", cx) .tooltip(Tooltip::text("This is a tooltip!")) .into_any_element(), )]) diff --git a/crates/ui_prompt/src/ui_prompt.rs b/crates/ui_prompt/src/ui_prompt.rs index fe6dc5b3f4..81ae969759 100644 --- a/crates/ui_prompt/src/ui_prompt.rs +++ b/crates/ui_prompt/src/ui_prompt.rs @@ -177,7 +177,7 @@ impl Render for ZedPromptRenderer { })) .child(h_flex().justify_end().gap_2().children( self.actions.iter().enumerate().rev().map(|(ix, action)| { - ui::Button::new(ix, action.clone()) + ui::Button::new(ix, action.clone(), cx) .label_size(LabelSize::Large) .style(ButtonStyle::Filled) .when(ix == self.active_action_id, |el| { diff --git a/crates/welcome/src/multibuffer_hint.rs b/crates/welcome/src/multibuffer_hint.rs index 3a20cbb6bd..b2252834f3 100644 --- a/crates/welcome/src/multibuffer_hint.rs +++ b/crates/welcome/src/multibuffer_hint.rs @@ -157,7 +157,7 @@ impl Render for MultibufferHint { )), ) .child( - Button::new("open_docs", "Learn More") + Button::new("open_docs", "Learn More", cx) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Small) .icon_color(Color::Muted) diff --git a/crates/welcome/src/welcome.rs b/crates/welcome/src/welcome.rs index b0a1c316f4..570370fffe 100644 --- a/crates/welcome/src/welcome.rs +++ b/crates/welcome/src/welcome.rs @@ -141,7 +141,7 @@ impl Render for WelcomePage { ), ) .child( - Button::new("choose-theme", "Choose a Theme") + Button::new("choose-theme", "Choose a Theme", cx) .icon(IconName::SwatchBook) .icon_size(IconSize::XSmall) .icon_color(Color::Muted) @@ -156,7 +156,7 @@ impl Render for WelcomePage { })), ) .child( - Button::new("choose-keymap", "Choose a Keymap") + Button::new("choose-keymap", "Choose a Keymap", cx) .icon(IconName::Keyboard) .icon_size(IconSize::XSmall) .icon_color(Color::Muted) @@ -179,6 +179,7 @@ impl Render for WelcomePage { Button::new( "edit_prediction_onboarding", edit_prediction_label, + cx, ) .disabled(edit_prediction_provider_is_zed) .icon(IconName::ZedPredict) @@ -194,7 +195,7 @@ impl Render for WelcomePage { ) }) .child( - Button::new("edit settings", "Edit Settings") + Button::new("edit settings", "Edit Settings", cx) .icon(IconName::Settings) .icon_size(IconSize::XSmall) .icon_color(Color::Muted) @@ -220,7 +221,7 @@ impl Render for WelcomePage { ) .when(cfg!(target_os = "macos"), |el| { el.child( - Button::new("install-cli", "Install the CLI") + Button::new("install-cli", "Install the CLI", cx) .icon(IconName::Terminal) .icon_size(IconSize::XSmall) .icon_color(Color::Muted) @@ -234,7 +235,7 @@ impl Render for WelcomePage { ) }) .child( - Button::new("view-docs", "View Documentation") + Button::new("view-docs", "View Documentation", cx) .icon(IconName::FileCode) .icon_size(IconSize::XSmall) .icon_color(Color::Muted) @@ -245,7 +246,7 @@ impl Render for WelcomePage { })), ) .child( - Button::new("explore-extensions", "Explore Extensions") + Button::new("explore-extensions", "Explore Extensions", cx) .icon(IconName::Blocks) .icon_size(IconSize::XSmall) .icon_color(Color::Muted) diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 96966435e1..23bfffb4ed 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -345,7 +345,7 @@ impl Render for LanguageServerPrompt { .child(Label::new(request.message.to_string()).size(LabelSize::Small)) .children(request.actions.iter().enumerate().map(|(ix, action)| { let this_handle = cx.entity().clone(); - Button::new(ix, action.title.clone()) + Button::new(ix, action.title.clone(), cx) .size(ButtonSize::Large) .on_click(move |_, window, cx| { let this_handle = this_handle.clone(); @@ -442,8 +442,12 @@ impl Render for ErrorMessagePrompt { .when_some(self.label_and_url_button.clone(), |elm, (label, url)| { elm.child( div().mt_2().child( - ui::Button::new("error_message_prompt_notification_button", label) - .on_click(move |_, _, cx| cx.open_url(&url)), + ui::Button::new( + "error_message_prompt_notification_button", + label, + cx, + ) + .on_click(move |_, _, cx| cx.open_url(&url)), ), ) }), @@ -715,7 +719,7 @@ pub mod simple_message_notification { h_flex() .gap_1() .children(self.primary_message.iter().map(|message| { - let mut button = Button::new(message.clone(), message.clone()) + let mut button = Button::new(message.clone(), message.clone(), cx) .label_size(LabelSize::Small) .on_click(cx.listener(|this, _, window, cx| { if let Some(on_click) = this.primary_on_click.as_ref() { @@ -735,7 +739,7 @@ pub mod simple_message_notification { button })) .children(self.secondary_message.iter().map(|message| { - let mut button = Button::new(message.clone(), message.clone()) + let mut button = Button::new(message.clone(), message.clone(), cx) .label_size(LabelSize::Small) .on_click(cx.listener(|this, _, window, cx| { if let Some(on_click) = this.secondary_on_click.as_ref() { @@ -761,7 +765,7 @@ pub mod simple_message_notification { .zip(self.more_info_url.iter()) .map(|(message, url)| { let url = url.clone(); - Button::new(message.clone(), message.clone()) + Button::new(message.clone(), message.clone(), cx) .label_size(LabelSize::Small) .icon(IconName::ArrowUpRight) .icon_size(IconSize::Indicator) diff --git a/crates/workspace/src/theme_preview.rs b/crates/workspace/src/theme_preview.rs index 03164e0a64..f4cafd7880 100644 --- a/crates/workspace/src/theme_preview.rs +++ b/crates/workspace/src/theme_preview.rs @@ -401,7 +401,7 @@ impl ThemePreview { .py_2() .bg(Self::preview_bg(window, cx)) .children(ThemePreviewPage::iter().map(|p| { - Button::new(ElementId::Name(p.name().into()), p.name()) + Button::new(ElementId::Name(p.name().into()), p.name(), cx) .on_click(cx.listener(move |this, _, window, cx| { this.current_page = p; cx.notify(); diff --git a/crates/zed/src/zed/component_preview.rs b/crates/zed/src/zed/component_preview.rs index ac889a7ad9..c47ebf066d 100644 --- a/crates/zed/src/zed/component_preview.rs +++ b/crates/zed/src/zed/component_preview.rs @@ -772,7 +772,7 @@ impl Render for ComponentPreview { .border_t_1() .border_color(cx.theme().colors().border) .child( - Button::new("toast-test", "Launch Toast") + Button::new("toast-test", "Launch Toast", cx) .full_width() .on_click(cx.listener({ move |this, _, _window, cx| { diff --git a/crates/zed/src/zed/migrate.rs b/crates/zed/src/zed/migrate.rs index 48bffb4114..0474e0b97d 100644 --- a/crates/zed/src/zed/migrate.rs +++ b/crates/zed/src/zed/migrate.rs @@ -235,7 +235,7 @@ impl Render for MigrationBanner { ), ) .child( - Button::new("backup-and-migrate", "Backup and Update").on_click( + Button::new("backup-and-migrate", "Backup and Update", cx).on_click( move |_, window, cx| { let fs = ::global(cx); match migration_type { diff --git a/crates/zeta/src/rate_completion_modal.rs b/crates/zeta/src/rate_completion_modal.rs index ac7fcade91..2ca8ed2aaa 100644 --- a/crates/zeta/src/rate_completion_modal.rs +++ b/crates/zeta/src/rate_completion_modal.rs @@ -313,6 +313,7 @@ impl RateCompletionModal { Button::new( ElementId::Name("suggested-edits".into()), RateCompletionView::SuggestedEdits.name(), + cx, ) .label_size(LabelSize::Small) .on_click(cx.listener(move |this, _, _window, cx| { @@ -325,6 +326,7 @@ impl RateCompletionModal { Button::new( ElementId::Name("raw-input".into()), RateCompletionView::RawInput.name(), + cx, ) .label_size(LabelSize::Small) .on_click(cx.listener(move |this, _, _window, cx| { @@ -489,7 +491,7 @@ impl RateCompletionModal { h_flex() .gap_1() .child( - Button::new("bad", "Bad Completion") + Button::new("bad", "Bad Completion", cx) .icon(IconName::ThumbsDown) .icon_size(IconSize::Small) .icon_position(IconPosition::Start) @@ -513,7 +515,7 @@ impl RateCompletionModal { })), ) .child( - Button::new("good", "Good Completion") + Button::new("good", "Good Completion", cx) .icon(IconName::ThumbsUp) .icon_size(IconSize::Small) .icon_position(IconPosition::Start)