agent: Add scrollbar to the settings view (#28814)

Danilo Leal created

Release Notes:

- agent: Added a scrollbar to the panel settings view.

Change summary

crates/agent/src/assistant_configuration.rs | 111 ++++++++++++++++------
1 file changed, 81 insertions(+), 30 deletions(-)

Detailed changes

crates/agent/src/assistant_configuration.rs 🔗

@@ -9,11 +9,14 @@ use assistant_tool::{ToolSource, ToolWorkingSet};
 use collections::HashMap;
 use context_server::manager::ContextServerManager;
 use fs::Fs;
-use gpui::{Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, Subscription};
+use gpui::{
+    Action, AnyView, App, Entity, EventEmitter, FocusHandle, Focusable, ScrollHandle, Subscription,
+};
 use language_model::{LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry};
 use settings::{Settings, update_settings_file};
 use ui::{
-    Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Switch, Tooltip, prelude::*,
+    Disclosure, Divider, DividerColor, ElevationIndex, Indicator, Scrollbar, ScrollbarState,
+    Switch, Tooltip, prelude::*,
 };
 use util::ResultExt as _;
 use zed_actions::ExtensionCategoryFilter;
@@ -31,6 +34,8 @@ pub struct AssistantConfiguration {
     expanded_context_server_tools: HashMap<Arc<str>, bool>,
     tools: Entity<ToolWorkingSet>,
     _registry_subscription: Subscription,
+    scroll_handle: ScrollHandle,
+    scrollbar_state: ScrollbarState,
 }
 
 impl AssistantConfiguration {
@@ -60,6 +65,9 @@ impl AssistantConfiguration {
             },
         );
 
+        let scroll_handle = ScrollHandle::new();
+        let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
+
         let mut this = Self {
             fs,
             focus_handle,
@@ -68,6 +76,8 @@ impl AssistantConfiguration {
             expanded_context_server_tools: HashMap::default(),
             tools,
             _registry_subscription: registry_subscription,
+            scroll_handle,
+            scrollbar_state,
         };
         this.build_provider_configuration_views(window, cx);
         this
@@ -109,7 +119,7 @@ pub enum AssistantConfigurationEvent {
 impl EventEmitter<AssistantConfigurationEvent> for AssistantConfiguration {}
 
 impl AssistantConfiguration {
-    fn render_provider_configuration(
+    fn render_provider_configuration_block(
         &mut self,
         provider: &Arc<dyn LanguageModelProvider>,
         cx: &mut Context<Self>,
@@ -164,7 +174,7 @@ impl AssistantConfiguration {
                     .p(DynamicSpacing::Base08.rems(cx))
                     .bg(cx.theme().colors().editor_background)
                     .border_1()
-                    .border_color(cx.theme().colors().border_variant)
+                    .border_color(cx.theme().colors().border)
                     .rounded_sm()
                     .map(|parent| match configuration_view {
                         Some(configuration_view) => parent.child(configuration_view),
@@ -175,6 +185,33 @@ impl AssistantConfiguration {
             )
     }
 
+    fn render_provider_configuration_section(
+        &mut self,
+        cx: &mut Context<Self>,
+    ) -> impl IntoElement {
+        let providers = LanguageModelRegistry::read_global(cx).providers();
+
+        v_flex()
+            .p(DynamicSpacing::Base16.rems(cx))
+            .pr(DynamicSpacing::Base20.rems(cx))
+            .gap_4()
+            .flex_1()
+            .child(
+                v_flex()
+                    .gap_0p5()
+                    .child(Headline::new("LLM Providers").size(HeadlineSize::Small))
+                    .child(
+                        Label::new("Add at least one provider to use AI-powered features.")
+                            .color(Color::Muted),
+                    ),
+            )
+            .children(
+                providers
+                    .into_iter()
+                    .map(|provider| self.render_provider_configuration_block(&provider, cx)),
+            )
+    }
+
     fn render_command_permission(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
         let always_allow_tool_actions = AssistantSettings::get_global(cx).always_allow_tool_actions;
 
@@ -182,6 +219,7 @@ impl AssistantConfiguration {
 
         v_flex()
             .p(DynamicSpacing::Base16.rems(cx))
+            .pr(DynamicSpacing::Base20.rems(cx))
             .gap_2()
             .flex_1()
             .child(Headline::new("General Settings").size(HeadlineSize::Small))
@@ -233,6 +271,7 @@ impl AssistantConfiguration {
 
         v_flex()
             .p(DynamicSpacing::Base16.rems(cx))
+            .pr(DynamicSpacing::Base20.rems(cx))
             .gap_2()
             .flex_1()
             .child(
@@ -426,39 +465,51 @@ impl AssistantConfiguration {
 
 impl Render for AssistantConfiguration {
     fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let providers = LanguageModelRegistry::read_global(cx).providers();
-
         v_flex()
             .id("assistant-configuration")
             .key_context("AgentConfiguration")
             .track_focus(&self.focus_handle(cx))
-            .bg(cx.theme().colors().panel_background)
+            .relative()
             .size_full()
-            .overflow_y_scroll()
-            .child(self.render_command_permission(cx))
-            .child(Divider::horizontal().color(DividerColor::Border))
-            .child(self.render_context_servers_section(cx))
-            .child(Divider::horizontal().color(DividerColor::Border))
+            .pb_8()
+            .bg(cx.theme().colors().panel_background)
             .child(
                 v_flex()
-                    .p(DynamicSpacing::Base16.rems(cx))
-                    .mt_1()
-                    .gap_6()
-                    .flex_1()
-                    .child(
-                        v_flex()
-                            .gap_0p5()
-                            .child(Headline::new("LLM Providers").size(HeadlineSize::Small))
-                            .child(
-                                Label::new("Add at least one provider to use AI-powered features.")
-                                    .color(Color::Muted),
-                            ),
-                    )
-                    .children(
-                        providers
-                            .into_iter()
-                            .map(|provider| self.render_provider_configuration(&provider, cx)),
-                    ),
+                    .id("assistant-configuration-content")
+                    .track_scroll(&self.scroll_handle)
+                    .size_full()
+                    .overflow_y_scroll()
+                    .child(self.render_command_permission(cx))
+                    .child(Divider::horizontal().color(DividerColor::Border))
+                    .child(self.render_context_servers_section(cx))
+                    .child(Divider::horizontal().color(DividerColor::Border))
+                    .child(self.render_provider_configuration_section(cx)),
+            )
+            .child(
+                div()
+                    .id("assistant-configuration-scrollbar")
+                    .occlude()
+                    .absolute()
+                    .right(px(3.))
+                    .top_0()
+                    .bottom_0()
+                    .pb_6()
+                    .w(px(12.))
+                    .cursor_default()
+                    .on_mouse_move(cx.listener(|_, _, _window, cx| {
+                        cx.notify();
+                        cx.stop_propagation()
+                    }))
+                    .on_hover(|_, _window, cx| {
+                        cx.stop_propagation();
+                    })
+                    .on_any_mouse_down(|_, _window, cx| {
+                        cx.stop_propagation();
+                    })
+                    .on_scroll_wheel(cx.listener(|_, _, _window, cx| {
+                        cx.notify();
+                    }))
+                    .children(Scrollbar::vertical(self.scrollbar_state.clone())),
             )
     }
 }