settings_ui: Fix edit predictions and tool permissions for narrow windows (#51878)

Finn Eitreim and Danilo Leal created

Closes #51865 

The Tool Permissions and Edit Prediction Providers pages have settings
that do not conform very well to the usual setup, because of this they
are specially defined in .../settings_ui/src/pages . Due to this setup,
they do not benefit from the same automatic responsive setup as the
other parts of the settings ui. This was causing issues w/ narrow
windows, where fields and text were extending past the side of the
window or just not displaying.
All that was needed to fix it was some tweaks to the structure and css
of those pages, and its smooth sailing. Maybe in the future
`render_settings_item` can be made broader and support these use cases
as well so this doesn't have to be handled manually.

Behavior Before:


https://github.com/user-attachments/assets/283df746-e1bb-4791-b259-085dc83f3292

Behavior After:


https://github.com/user-attachments/assets/154c8fcf-8a02-49c8-910a-a69dc11b144f

Before you mark this PR as ready for review, make sure that you have:
- [x] Added a solid test coverage and/or screenshots from doing manual
testing
- [x] Done a self-review taking into account security and performance
aspects
- [x] Aligned any UI changes with the [UI
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)

Release Notes:

- Settings Editor: Fixed the display issue with narrow windows on the
Edit Prediction configuration page.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

crates/settings_ui/src/pages/edit_prediction_provider_setup.rs | 15 +
crates/settings_ui/src/pages/tool_permissions_setup.rs         | 21 ++
crates/settings_ui/src/settings_ui.rs                          | 29 ++-
3 files changed, 43 insertions(+), 22 deletions(-)

Detailed changes

crates/settings_ui/src/pages/edit_prediction_provider_setup.rs 🔗

@@ -99,8 +99,7 @@ pub(crate) fn render_edit_prediction_setup_page(
                 IconName::AiOpenAiCompat,
                 "OpenAI Compatible API",
                 ApiKeyDocs::Custom {
-                    message: "Set an API key here. It will be sent as Authorization: Bearer {key}."
-                        .into(),
+                    message: "The API key sent as Authorization: Bearer {key}.".into(),
                 },
                 open_ai_compatible_api_token(cx),
                 |cx| open_ai_compatible_api_url(cx),
@@ -172,10 +171,12 @@ fn render_provider_dropdown(window: &mut Window, cx: &mut App) -> AnyElement {
             h_flex()
                 .pt_2p5()
                 .w_full()
+                .min_w_0()
                 .justify_between()
                 .child(
                     v_flex()
                         .w_full()
+                        .min_w_0()
                         .max_w_1_2()
                         .child(Label::new("Provider"))
                         .child(
@@ -246,13 +247,15 @@ fn render_api_key_provider(
         .no_padding(true);
     let button_link_label = format!("{} dashboard", title);
     let description = match docs {
-        ApiKeyDocs::Custom { message } => h_flex().min_w_0().gap_0p5().child(
+        ApiKeyDocs::Custom { message } => div().min_w_0().w_full().child(
             Label::new(message)
                 .size(LabelSize::Small)
                 .color(Color::Muted),
         ),
         ApiKeyDocs::Link { dashboard_url } => h_flex()
+            .w_full()
             .min_w_0()
+            .flex_wrap()
             .gap_0p5()
             .child(
                 Label::new("Visit the")
@@ -300,10 +303,12 @@ fn render_api_key_provider(
             h_flex()
                 .pt_2p5()
                 .w_full()
+                .min_w_0()
                 .justify_between()
                 .child(
                     v_flex()
                         .w_full()
+                        .min_w_0()
                         .max_w_1_2()
                         .child(Label::new("API Key"))
                         .child(description)
@@ -466,7 +471,7 @@ fn ollama_settings() -> Box<[SettingsPageItem]> {
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Prompt Format",
-            description: "The prompt format to use when requesting predictions. Set to Infer to have the format inferred based on the model name",
+            description: "The prompt format to use when requesting predictions. Set to Infer to have the format inferred based on the model name.",
             field: Box::new(SettingField {
                 pick: |settings| {
                     settings
@@ -597,7 +602,7 @@ fn open_ai_compatible_settings() -> Box<[SettingsPageItem]> {
         }),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Prompt Format",
-            description: "The prompt format to use when requesting predictions. Set to Infer to have the format inferred based on the model name",
+            description: "The prompt format to use when requesting predictions. Set to Infer to have the format inferred based on the model name.",
             field: Box::new(SettingField {
                 pick: |settings| {
                     settings

crates/settings_ui/src/pages/tool_permissions_setup.rs 🔗

@@ -249,10 +249,13 @@ fn render_tool_list_item(
 
     h_flex()
         .w_full()
+        .min_w_0()
         .py_3()
         .justify_between()
         .child(
             v_flex()
+                .w_full()
+                .min_w_0()
                 .child(h_flex().gap_1().child(Label::new(tool.name)).when_some(
                     rule_summary,
                     |this, summary| {
@@ -1072,9 +1075,12 @@ fn render_global_default_mode_section(current_mode: ToolPermissionMode) -> AnyEl
 
     h_flex()
         .my_4()
+        .min_w_0()
         .justify_between()
         .child(
             v_flex()
+                .w_full()
+                .min_w_0()
                 .child(Label::new("Default Permission"))
                 .child(
                     Label::new(
@@ -1125,13 +1131,18 @@ fn render_default_mode_section(
     let tool_id_owned = tool_id.to_string();
 
     h_flex()
+        .min_w_0()
         .justify_between()
         .child(
-            v_flex().child(Label::new("Default Action")).child(
-                Label::new("Action to take when no patterns match.")
-                    .size(LabelSize::Small)
-                    .color(Color::Muted),
-            ),
+            v_flex()
+                .w_full()
+                .min_w_0()
+                .child(Label::new("Default Action"))
+                .child(
+                    Label::new("Action to take when no patterns match.")
+                        .size(LabelSize::Small)
+                        .color(Color::Muted),
+                ),
         )
         .child(
             PopoverMenu::new(format!("default-mode-{}", tool_id))

crates/settings_ui/src/settings_ui.rs 🔗

@@ -2883,7 +2883,7 @@ impl SettingsWindow {
     }
 
     fn render_sub_page_breadcrumbs(&self) -> impl IntoElement {
-        h_flex().gap_1().children(
+        h_flex().min_w_0().gap_1().overflow_x_hidden().children(
             itertools::intersperse(
                 std::iter::once(self.current_page().title.into()).chain(
                     self.sub_page_stack
@@ -3113,9 +3113,11 @@ impl SettingsWindow {
         if let Some(current_sub_page) = self.sub_page_stack.last() {
             page_header = h_flex()
                 .w_full()
+                .min_w_0()
                 .justify_between()
                 .child(
                     h_flex()
+                        .min_w_0()
                         .ml_neg_1p5()
                         .gap_1()
                         .child(
@@ -3130,17 +3132,19 @@ impl SettingsWindow {
                 )
                 .when(current_sub_page.link.in_json, |this| {
                     this.child(
-                        Button::new("open-in-settings-file", "Edit in settings.json")
-                            .tab_index(0_isize)
-                            .style(ButtonStyle::OutlinedGhost)
-                            .tooltip(Tooltip::for_action_title_in(
-                                "Edit in settings.json",
-                                &OpenCurrentFile,
-                                &self.focus_handle,
-                            ))
-                            .on_click(cx.listener(|this, _, window, cx| {
-                                this.open_current_settings_file(window, cx);
-                            })),
+                        div().flex_shrink_0().child(
+                            Button::new("open-in-settings-file", "Edit in settings.json")
+                                .tab_index(0_isize)
+                                .style(ButtonStyle::OutlinedGhost)
+                                .tooltip(Tooltip::for_action_title_in(
+                                    "Edit in settings.json",
+                                    &OpenCurrentFile,
+                                    &self.focus_handle,
+                                ))
+                                .on_click(cx.listener(|this, _, window, cx| {
+                                    this.open_current_settings_file(window, cx);
+                                })),
+                        ),
                     )
                 })
                 .into_any_element();
@@ -3310,6 +3314,7 @@ impl SettingsWindow {
             .pt_6()
             .gap_4()
             .flex_1()
+            .min_w_0()
             .bg(cx.theme().colors().editor_background)
             .child(
                 v_flex()