settings_ui: Move LSP & tool settings from Editor to Language page (#40140)

Ben Kunkle created

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

crates/settings_ui/src/page_data.rs   | 573 +++++++++++++++-------------
crates/settings_ui/src/settings_ui.rs |  12 
2 files changed, 311 insertions(+), 274 deletions(-)

Detailed changes

crates/settings_ui/src/page_data.rs 🔗

@@ -1405,18 +1405,23 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
             },
         },
         SettingsPage {
-            title: "Languages",
+            title: "Languages & Tools",
             items: {
-                let mut items = vec![SettingsPageItem::SectionHeader(LANGUAGES_SECTION_HEADER)];
+                let mut items = vec![];
+                items.extend(non_editor_language_settings_data());
                 // todo(settings_ui): Refresh on extension (un)/installed
                 // Note that `crates/json_schema_store` solves the same problem, there is probably a way to unify the two
+                items.push(SettingsPageItem::SectionHeader(LANGUAGES_SECTION_HEADER));
                 items.extend(all_language_names(cx).into_iter().map(|language_name| {
                     SettingsPageItem::SubPageLink(SubPageLink {
                         title: language_name,
                         files: USER | LOCAL,
                         render: Arc::new(|this, window, cx| {
                             this.render_page_items(
-                                language_settings_data().iter().enumerate(),
+                                language_settings_data()
+                                    .iter()
+                                    .chain(non_editor_language_settings_data().iter())
+                                    .enumerate(),
                                 None,
                                 window,
                                 cx,
@@ -4629,47 +4634,47 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
 
 const LANGUAGES_SECTION_HEADER: &'static str = "Languages";
 
-fn language_settings_data() -> Vec<SettingsPageItem> {
-    fn current_language() -> Option<SharedString> {
-        sub_page_stack().iter().find_map(|page| {
-            (page.section_header == LANGUAGES_SECTION_HEADER).then(|| page.link.title.clone())
-        })
-    }
+fn current_language() -> Option<SharedString> {
+    sub_page_stack().iter().find_map(|page| {
+        (page.section_header == LANGUAGES_SECTION_HEADER).then(|| page.link.title.clone())
+    })
+}
 
-    fn language_settings_field<T>(
-        settings_content: &SettingsContent,
-        get: fn(&LanguageSettingsContent) -> &Option<T>,
-    ) -> &Option<T> {
-        let all_languages = &settings_content.project.all_languages;
-        if let Some(current_language_name) = current_language() {
-            if let Some(current_language) = all_languages.languages.0.get(&current_language_name) {
-                let value = get(current_language);
-                if value.is_some() {
-                    return value;
-                }
+fn language_settings_field<T>(
+    settings_content: &SettingsContent,
+    get: fn(&LanguageSettingsContent) -> &Option<T>,
+) -> &Option<T> {
+    let all_languages = &settings_content.project.all_languages;
+    if let Some(current_language_name) = current_language() {
+        if let Some(current_language) = all_languages.languages.0.get(&current_language_name) {
+            let value = get(current_language);
+            if value.is_some() {
+                return value;
             }
         }
-        let default_value = get(&all_languages.defaults);
-        return default_value;
     }
+    let default_value = get(&all_languages.defaults);
+    return default_value;
+}
 
-    fn language_settings_field_mut<T>(
-        settings_content: &mut SettingsContent,
-        get: fn(&mut LanguageSettingsContent) -> &mut Option<T>,
-    ) -> &mut Option<T> {
-        let all_languages = &mut settings_content.project.all_languages;
-        let language_content = if let Some(current_language) = current_language() {
-            all_languages
-                .languages
-                .0
-                .entry(current_language)
-                .or_default()
-        } else {
-            &mut all_languages.defaults
-        };
-        return get(language_content);
-    }
+fn language_settings_field_mut<T>(
+    settings_content: &mut SettingsContent,
+    get: fn(&mut LanguageSettingsContent) -> &mut Option<T>,
+) -> &mut Option<T> {
+    let all_languages = &mut settings_content.project.all_languages;
+    let language_content = if let Some(current_language) = current_language() {
+        all_languages
+            .languages
+            .0
+            .entry(current_language)
+            .or_default()
+    } else {
+        &mut all_languages.defaults
+    };
+    return get(language_content);
+}
 
+fn language_settings_data() -> Vec<SettingsPageItem> {
     vec![
         SettingsPageItem::SectionHeader("Indentation"),
         SettingsPageItem::SettingItem(SettingItem {
@@ -5034,101 +5039,6 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
             metadata: None,
             files: USER | LOCAL,
         }),
-        SettingsPageItem::SectionHeader("Prettier"),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Allowed",
-            description: "Enables or disables formatting with Prettier for a given language",
-            field: Box::new(SettingField {
-                pick: |settings_content| {
-                    language_settings_field(settings_content, |language| {
-                        if let Some(prettier) = &language.prettier {
-                            &prettier.allowed
-                        } else {
-                            &None
-                        }
-                    })
-                },
-                pick_mut: |settings_content| {
-                    language_settings_field_mut(settings_content, |language| {
-                        &mut language.prettier.get_or_insert_default().allowed
-                    })
-                },
-            }),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Parser",
-            description: "Forces Prettier integration to use a specific parser name when formatting files with the language",
-            field: Box::new(SettingField {
-                pick: |settings_content| {
-                    language_settings_field(settings_content, |language| {
-                        if let Some(prettier) = &language.prettier {
-                            &prettier.parser
-                        } else {
-                            &None
-                        }
-                    })
-                },
-                pick_mut: |settings_content| {
-                    language_settings_field_mut(settings_content, |language| {
-                        &mut language.prettier.get_or_insert_default().parser
-                    })
-                },
-            }),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Plugins",
-            description: "Forces Prettier integration to use specific plugins when formatting files with the language",
-            field: Box::new(
-                SettingField {
-                    pick: |settings_content| {
-                        language_settings_field(settings_content, |language| {
-                            if let Some(prettier) = &language.prettier {
-                                &prettier.plugins
-                            } else {
-                                &None
-                            }
-                        })
-                    },
-                    pick_mut: |settings_content| {
-                        language_settings_field_mut(settings_content, |language| {
-                            &mut language.prettier.get_or_insert_default().plugins
-                        })
-                    },
-                }
-                .unimplemented(),
-            ),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Options",
-            description: "Default Prettier options, in the format as in package.json section for Prettier",
-            field: Box::new(
-                SettingField {
-                    pick: |settings_content| {
-                        language_settings_field(settings_content, |language| {
-                            if let Some(prettier) = &language.prettier {
-                                &prettier.options
-                            } else {
-                                &None
-                            }
-                        })
-                    },
-                    pick_mut: |settings_content| {
-                        language_settings_field_mut(settings_content, |language| {
-                            &mut language.prettier.get_or_insert_default().options
-                        })
-                    },
-                }
-                .unimplemented(),
-            ),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
         SettingsPageItem::SectionHeader("Autoclose"),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Use Autoclose",
@@ -5204,72 +5114,6 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
             metadata: None,
             files: USER | LOCAL,
         }),
-        SettingsPageItem::SectionHeader("LSP"),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Enable Language Server",
-            description: "Whether to use language servers to provide code intelligence",
-            field: Box::new(SettingField {
-                pick: |settings_content| {
-                    language_settings_field(settings_content, |language| {
-                        &language.enable_language_server
-                    })
-                },
-                pick_mut: |settings_content| {
-                    language_settings_field_mut(settings_content, |language| {
-                        &mut language.enable_language_server
-                    })
-                },
-            }),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Language Servers",
-            description: "The list of language servers to use (or disable) for this language",
-            field: Box::new(
-                SettingField {
-                    pick: |settings_content| {
-                        language_settings_field(settings_content, |language| {
-                            &language.language_servers
-                        })
-                    },
-                    pick_mut: |settings_content| {
-                        language_settings_field_mut(settings_content, |language| {
-                            &mut language.language_servers
-                        })
-                    },
-                }
-                .unimplemented(),
-            ),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Linked Edits",
-            description: "Whether to perform linked edits of associated ranges, if the LS supports it. For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well",
-            field: Box::new(SettingField {
-                pick: |settings_content| {
-                    language_settings_field(settings_content, |language| &language.linked_edits)
-                },
-                pick_mut: |settings_content| {
-                    language_settings_field_mut(settings_content, |language| {
-                        &mut language.linked_edits
-                    })
-                },
-            }),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Go To Definition Fallback",
-            description: "Whether to follow-up empty go to definition responses from the language server",
-            field: Box::new(SettingField {
-                pick: |settings_content| &settings_content.editor.go_to_definition_fallback,
-                pick_mut: |settings_content| &mut settings_content.editor.go_to_definition_fallback,
-            }),
-            metadata: None,
-            files: USER,
-        }),
         SettingsPageItem::SectionHeader("Edit Predictions"),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Show Edit Predictions",
@@ -5461,75 +5305,6 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
             metadata: None,
             files: USER | LOCAL,
         }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Lsp",
-            description: "Whether to fetch LSP completions or not",
-            field: Box::new(SettingField {
-                pick: |settings_content| {
-                    language_settings_field(settings_content, |language| {
-                        if let Some(completions) = &language.completions {
-                            &completions.lsp
-                        } else {
-                            &None
-                        }
-                    })
-                },
-                pick_mut: |settings_content| {
-                    language_settings_field_mut(settings_content, |language| {
-                        &mut language.completions.get_or_insert_default().lsp
-                    })
-                },
-            }),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Lsp Fetch Timeout Ms",
-            description: "When fetching LSP completions, determines how long to wait for a response of a particular server (set to 0 to wait indefinitely)",
-            field: Box::new(SettingField {
-                pick: |settings_content| {
-                    language_settings_field(settings_content, |language| {
-                        if let Some(completions) = &language.completions {
-                            &completions.lsp_fetch_timeout_ms
-                        } else {
-                            &None
-                        }
-                    })
-                },
-                pick_mut: |settings_content| {
-                    language_settings_field_mut(settings_content, |language| {
-                        &mut language
-                            .completions
-                            .get_or_insert_default()
-                            .lsp_fetch_timeout_ms
-                    })
-                },
-            }),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
-        SettingsPageItem::SettingItem(SettingItem {
-            title: "Lsp Insert Mode",
-            description: "Controls how LSP completions are inserted",
-            field: Box::new(SettingField {
-                pick: |settings_content| {
-                    language_settings_field(settings_content, |language| {
-                        if let Some(completions) = &language.completions {
-                            &completions.lsp_insert_mode
-                        } else {
-                            &None
-                        }
-                    })
-                },
-                pick_mut: |settings_content| {
-                    language_settings_field_mut(settings_content, |language| {
-                        &mut language.completions.get_or_insert_default().lsp_insert_mode
-                    })
-                },
-            }),
-            metadata: None,
-            files: USER | LOCAL,
-        }),
         SettingsPageItem::SectionHeader("Inlay Hints"),
         SettingsPageItem::SettingItem(SettingItem {
             title: "Enabled",
@@ -5799,8 +5574,8 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
             files: USER | LOCAL,
         }),
         SettingsPageItem::SettingItem(SettingItem {
-            title: "Prefer Lsp",
-            description: "Use LSP tasks over Zed language extension ones",
+            title: "Prefer LSP",
+            description: "Use LSP tasks over Zed language extension tasks",
             field: Box::new(SettingField {
                 pick: |settings_content| {
                     language_settings_field(settings_content, |language| {
@@ -5870,3 +5645,261 @@ fn language_settings_data() -> Vec<SettingsPageItem> {
         }),
     ]
 }
+
+/// LanguageSettings items that should be included in the "Languages & Tools" page
+/// not the "Editor" page
+fn non_editor_language_settings_data() -> Vec<SettingsPageItem> {
+    vec![
+        SettingsPageItem::SectionHeader("LSP"),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Enable Language Server",
+            description: "Whether to use language servers to provide code intelligence",
+            field: Box::new(SettingField {
+                pick: |settings_content| {
+                    language_settings_field(settings_content, |language| {
+                        &language.enable_language_server
+                    })
+                },
+                pick_mut: |settings_content| {
+                    language_settings_field_mut(settings_content, |language| {
+                        &mut language.enable_language_server
+                    })
+                },
+            }),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Language Servers",
+            description: "The list of language servers to use (or disable) for this language",
+            field: Box::new(
+                SettingField {
+                    pick: |settings_content| {
+                        language_settings_field(settings_content, |language| {
+                            &language.language_servers
+                        })
+                    },
+                    pick_mut: |settings_content| {
+                        language_settings_field_mut(settings_content, |language| {
+                            &mut language.language_servers
+                        })
+                    },
+                }
+                .unimplemented(),
+            ),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Linked Edits",
+            description: "Whether to perform linked edits of associated ranges, if the LS supports it. For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well",
+            field: Box::new(SettingField {
+                pick: |settings_content| {
+                    language_settings_field(settings_content, |language| &language.linked_edits)
+                },
+                pick_mut: |settings_content| {
+                    language_settings_field_mut(settings_content, |language| {
+                        &mut language.linked_edits
+                    })
+                },
+            }),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Go To Definition Fallback",
+            description: "Whether to follow-up empty go to definition responses from the language server",
+            field: Box::new(SettingField {
+                pick: |settings_content| &settings_content.editor.go_to_definition_fallback,
+                pick_mut: |settings_content| &mut settings_content.editor.go_to_definition_fallback,
+            }),
+            metadata: None,
+            files: USER,
+        }),
+        SettingsPageItem::SectionHeader("LSP Completions"),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Enabled",
+            description: "Whether to fetch LSP completions or not",
+            field: Box::new(SettingField {
+                pick: |settings_content| {
+                    language_settings_field(settings_content, |language| {
+                        if let Some(completions) = &language.completions {
+                            &completions.lsp
+                        } else {
+                            &None
+                        }
+                    })
+                },
+                pick_mut: |settings_content| {
+                    language_settings_field_mut(settings_content, |language| {
+                        &mut language.completions.get_or_insert_default().lsp
+                    })
+                },
+            }),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Fetch Timeout (milliseconds)",
+            description: "When fetching LSP completions, determines how long to wait for a response of a particular server (set to 0 to wait indefinitely)",
+            field: Box::new(SettingField {
+                pick: |settings_content| {
+                    language_settings_field(settings_content, |language| {
+                        if let Some(completions) = &language.completions {
+                            &completions.lsp_fetch_timeout_ms
+                        } else {
+                            &None
+                        }
+                    })
+                },
+                pick_mut: |settings_content| {
+                    language_settings_field_mut(settings_content, |language| {
+                        &mut language
+                            .completions
+                            .get_or_insert_default()
+                            .lsp_fetch_timeout_ms
+                    })
+                },
+            }),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Insert Mode",
+            description: "Controls how LSP completions are inserted",
+            field: Box::new(SettingField {
+                pick: |settings_content| {
+                    language_settings_field(settings_content, |language| {
+                        if let Some(completions) = &language.completions {
+                            &completions.lsp_insert_mode
+                        } else {
+                            &None
+                        }
+                    })
+                },
+                pick_mut: |settings_content| {
+                    language_settings_field_mut(settings_content, |language| {
+                        &mut language.completions.get_or_insert_default().lsp_insert_mode
+                    })
+                },
+            }),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SectionHeader("Debuggers"),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Debuggers",
+            description: "Preferred debuggers for this language",
+            field: Box::new(
+                SettingField {
+                    pick: |settings_content| {
+                        language_settings_field(settings_content, |language| &language.debuggers)
+                    },
+                    pick_mut: |settings_content| {
+                        language_settings_field_mut(settings_content, |language| {
+                            &mut language.debuggers
+                        })
+                    },
+                }
+                .unimplemented(),
+            ),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SectionHeader("Prettier"),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Allowed",
+            description: "Enables or disables formatting with Prettier for a given language",
+            field: Box::new(SettingField {
+                pick: |settings_content| {
+                    language_settings_field(settings_content, |language| {
+                        if let Some(prettier) = &language.prettier {
+                            &prettier.allowed
+                        } else {
+                            &None
+                        }
+                    })
+                },
+                pick_mut: |settings_content| {
+                    language_settings_field_mut(settings_content, |language| {
+                        &mut language.prettier.get_or_insert_default().allowed
+                    })
+                },
+            }),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Parser",
+            description: "Forces Prettier integration to use a specific parser name when formatting files with the language",
+            field: Box::new(SettingField {
+                pick: |settings_content| {
+                    language_settings_field(settings_content, |language| {
+                        if let Some(prettier) = &language.prettier {
+                            &prettier.parser
+                        } else {
+                            &None
+                        }
+                    })
+                },
+                pick_mut: |settings_content| {
+                    language_settings_field_mut(settings_content, |language| {
+                        &mut language.prettier.get_or_insert_default().parser
+                    })
+                },
+            }),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Plugins",
+            description: "Forces Prettier integration to use specific plugins when formatting files with the language",
+            field: Box::new(
+                SettingField {
+                    pick: |settings_content| {
+                        language_settings_field(settings_content, |language| {
+                            if let Some(prettier) = &language.prettier {
+                                &prettier.plugins
+                            } else {
+                                &None
+                            }
+                        })
+                    },
+                    pick_mut: |settings_content| {
+                        language_settings_field_mut(settings_content, |language| {
+                            &mut language.prettier.get_or_insert_default().plugins
+                        })
+                    },
+                }
+                .unimplemented(),
+            ),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+        SettingsPageItem::SettingItem(SettingItem {
+            title: "Options",
+            description: "Default Prettier options, in the format as in package.json section for Prettier",
+            field: Box::new(
+                SettingField {
+                    pick: |settings_content| {
+                        language_settings_field(settings_content, |language| {
+                            if let Some(prettier) = &language.prettier {
+                                &prettier.options
+                            } else {
+                                &None
+                            }
+                        })
+                    },
+                    pick_mut: |settings_content| {
+                        language_settings_field_mut(settings_content, |language| {
+                            &mut language.prettier.get_or_insert_default().options
+                        })
+                    },
+                }
+                .unimplemented(),
+            ),
+            metadata: None,
+            files: USER | LOCAL,
+        }),
+    ]
+}

crates/settings_ui/src/settings_ui.rs 🔗

@@ -668,10 +668,14 @@ impl SettingsPageItem {
                 .gap_2()
                 .justify_between()
                 .pt_4()
-                .when(!is_last, |this| {
-                    this.pb_4()
-                        .border_b_1()
-                        .border_color(cx.theme().colors().border_variant)
+                .map(|this| {
+                    if is_last {
+                        this.pb_10()
+                    } else {
+                        this.pb_4()
+                            .border_b_1()
+                            .border_color(cx.theme().colors().border_variant)
+                    }
                 })
                 .child(
                     v_flex()