Terminal settings too

Conrad Irwin created

Change summary

crates/settings/src/settings_content.rs          |   5 
crates/settings/src/settings_content/terminal.rs | 314 ++++++++++++
crates/terminal/src/terminal_settings.rs         | 438 ++++++-----------
crates/theme/src/settings.rs                     |   9 
4 files changed, 470 insertions(+), 296 deletions(-)

Detailed changes

crates/settings/src/settings_content.rs 🔗

@@ -1,6 +1,8 @@
 mod language;
+mod terminal;
 mod theme;
 pub use language::*;
+pub use terminal::*;
 pub use theme::*;
 
 use std::env;
@@ -41,6 +43,9 @@ pub struct SettingsContent {
     /// Example: {"log": {"client": "warn"}}
     pub log: Option<HashMap<String, String>>,
 
+    /// Configuration of the terminal in Zed.
+    pub terminal: Option<TerminalSettingsContent>,
+
     pub title_bar: Option<TitleBarSettingsContent>,
 
     /// Whether or not to enable Vim mode.

crates/settings/src/settings_content/terminal.rs 🔗

@@ -0,0 +1,314 @@
+use std::path::PathBuf;
+
+use collections::HashMap;
+use gpui::{AbsoluteLength, FontFeatures, SharedString, px};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+
+use crate::FontFamilyName;
+
+#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
+pub struct TerminalSettingsContent {
+    /// What shell to use when opening a terminal.
+    ///
+    /// Default: system
+    pub shell: Option<Shell>,
+    /// What working directory to use when launching the terminal
+    ///
+    /// Default: current_project_directory
+    pub working_directory: Option<WorkingDirectory>,
+    /// Sets the terminal's font size.
+    ///
+    /// If this option is not included,
+    /// the terminal will default to matching the buffer's font size.
+    pub font_size: Option<f32>,
+    /// Sets the terminal's font family.
+    ///
+    /// If this option is not included,
+    /// the terminal will default to matching the buffer's font family.
+    pub font_family: Option<FontFamilyName>,
+
+    /// Sets the terminal's font fallbacks.
+    ///
+    /// If this option is not included,
+    /// the terminal will default to matching the buffer's font fallbacks.
+    #[schemars(extend("uniqueItems" = true))]
+    pub font_fallbacks: Option<Vec<FontFamilyName>>,
+
+    /// Sets the terminal's line height.
+    ///
+    /// Default: comfortable
+    pub line_height: Option<TerminalLineHeight>,
+    pub font_features: Option<FontFeatures>,
+    /// Sets the terminal's font weight in CSS weight units 0-900.
+    pub font_weight: Option<f32>,
+    /// Any key-value pairs added to this list will be added to the terminal's
+    /// environment. Use `:` to separate multiple values.
+    ///
+    /// Default: {}
+    pub env: Option<HashMap<String, String>>,
+    /// Default cursor shape for the terminal.
+    /// Can be "bar", "block", "underline", or "hollow".
+    ///
+    /// Default: None
+    pub cursor_shape: Option<CursorShapeContent>,
+    /// Sets the cursor blinking behavior in the terminal.
+    ///
+    /// Default: terminal_controlled
+    pub blinking: Option<TerminalBlink>,
+    /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
+    /// Alternate Scroll mode converts mouse scroll events into up / down key
+    /// presses when in the alternate screen (e.g. when running applications
+    /// like vim or  less). The terminal can still set and unset this mode.
+    ///
+    /// Default: on
+    pub alternate_scroll: Option<AlternateScroll>,
+    /// Sets whether the option key behaves as the meta key.
+    ///
+    /// Default: false
+    pub option_as_meta: Option<bool>,
+    /// Whether or not selecting text in the terminal will automatically
+    /// copy to the system clipboard.
+    ///
+    /// Default: false
+    pub copy_on_select: Option<bool>,
+    /// Whether to keep the text selection after copying it to the clipboard.
+    ///
+    /// Default: false
+    pub keep_selection_on_copy: Option<bool>,
+    /// Whether to show the terminal button in the status bar.
+    ///
+    /// Default: true
+    pub button: Option<bool>,
+    pub dock: Option<TerminalDockPosition>,
+    /// Default width when the terminal is docked to the left or right.
+    ///
+    /// Default: 640
+    pub default_width: Option<f32>,
+    /// Default height when the terminal is docked to the bottom.
+    ///
+    /// Default: 320
+    pub default_height: Option<f32>,
+    /// Activates the python virtual environment, if one is found, in the
+    /// terminal's working directory (as resolved by the working_directory
+    /// setting). Set this to "off" to disable this behavior.
+    ///
+    /// Default: on
+    pub detect_venv: Option<VenvSettings>,
+    /// The maximum number of lines to keep in the scrollback history.
+    /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
+    /// 0 disables the scrolling.
+    /// Existing terminals will not pick up this change until they are recreated.
+    /// See <a href="https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213">Alacritty documentation</a> for more information.
+    ///
+    /// Default: 10_000
+    pub max_scroll_history_lines: Option<usize>,
+    /// Toolbar related settings
+    pub toolbar: Option<TerminalToolbarContent>,
+    /// Scrollbar-related settings
+    pub scrollbar: Option<ScrollbarSettingsContent>,
+    /// The minimum APCA perceptual contrast between foreground and background colors.
+    ///
+    /// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
+    /// especially for dark mode. Values range from 0 to 106.
+    ///
+    /// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
+    /// https://readtech.org/ARC/tests/bronze-simple-mode/
+    /// - 0: No contrast adjustment
+    /// - 45: Minimum for large fluent text (36px+)
+    /// - 60: Minimum for other content text
+    /// - 75: Minimum for body text
+    /// - 90: Preferred for body text
+    ///
+    /// Default: 45
+    pub minimum_contrast: Option<f32>,
+}
+
+/// Shell configuration to open the terminal with.
+#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum Shell {
+    /// Use the system's default terminal configuration in /etc/passwd
+    #[default]
+    System,
+    /// Use a specific program with no arguments.
+    Program(String),
+    /// Use a specific program with arguments.
+    WithArguments {
+        /// The program to run.
+        program: String,
+        /// The arguments to pass to the program.
+        args: Vec<String>,
+        /// An optional string to override the title of the terminal tab
+        title_override: Option<SharedString>,
+    },
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum WorkingDirectory {
+    /// Use the current file's project directory.  Will Fallback to the
+    /// first project directory strategy if unsuccessful.
+    CurrentProjectDirectory,
+    /// Use the first project in this workspace's directory.
+    FirstProjectDirectory,
+    /// Always use this platform's home directory (if it can be found).
+    AlwaysHome,
+    /// Always use a specific directory. This value will be shell expanded.
+    /// If this path is not a valid directory the terminal will default to
+    /// this platform's home directory  (if it can be found).
+    Always { directory: String },
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct ScrollbarSettingsContent {
+    /// When to show the scrollbar in the terminal.
+    ///
+    /// Default: inherits editor scrollbar settings
+    pub show: Option<Option<ShowScrollbar>>,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
+#[serde(rename_all = "snake_case")]
+pub enum TerminalLineHeight {
+    /// Use a line height that's comfortable for reading, 1.618
+    #[default]
+    Comfortable,
+    /// Use a standard line height, 1.3. This option is useful for TUIs,
+    /// particularly if they use box characters
+    Standard,
+    /// Use a custom line height.
+    Custom(f32),
+}
+
+impl TerminalLineHeight {
+    pub fn value(&self) -> AbsoluteLength {
+        let value = match self {
+            TerminalLineHeight::Comfortable => 1.618,
+            TerminalLineHeight::Standard => 1.3,
+            TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
+        };
+        px(value).into()
+    }
+}
+
+/// When to show the scrollbar in the terminal.
+///
+/// Default: auto
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub enum ShowScrollbar {
+    /// Show the scrollbar if there's important information or
+    /// follow the system's configured behavior.
+    Auto,
+    /// Match the system's configured behavior.
+    System,
+    /// Always show the scrollbar.
+    Always,
+    /// Never show the scrollbar.
+    Never,
+}
+
+#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum CursorShapeContent {
+    /// Cursor is a block like `█`.
+    #[default]
+    Block,
+    /// Cursor is an underscore like `_`.
+    Underline,
+    /// Cursor is a vertical bar like `⎸`.
+    Bar,
+    /// Cursor is a hollow box like `▯`.
+    Hollow,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum TerminalBlink {
+    /// Never blink the cursor, ignoring the terminal mode.
+    Off,
+    /// Default the cursor blink to off, but allow the terminal to
+    /// set blinking.
+    TerminalControlled,
+    /// Always blink the cursor, ignoring the terminal mode.
+    On,
+}
+
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum AlternateScroll {
+    On,
+    Off,
+}
+
+// Toolbar related settings
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct TerminalToolbarContent {
+    /// Whether to display the terminal title in breadcrumbs inside the terminal pane.
+    /// Only shown if the terminal title is not empty.
+    ///
+    /// The shell running in the terminal needs to be configured to emit the title.
+    /// Example: `echo -e "\e]2;New Title\007";`
+    ///
+    /// Default: true
+    pub breadcrumbs: Option<bool>,
+}
+
+#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum VenvSettings {
+    #[default]
+    Off,
+    On {
+        /// Default directories to search for virtual environments, relative
+        /// to the current working directory. We recommend overriding this
+        /// in your project's settings, rather than globally.
+        activate_script: Option<ActivateScript>,
+        venv_name: Option<String>,
+        directories: Option<Vec<PathBuf>>,
+    },
+}
+
+pub struct VenvSettingsContent<'a> {
+    pub activate_script: ActivateScript,
+    pub venv_name: &'a str,
+    pub directories: &'a [PathBuf],
+}
+
+impl VenvSettings {
+    pub fn as_option(&self) -> Option<VenvSettingsContent<'_>> {
+        match self {
+            VenvSettings::Off => None,
+            VenvSettings::On {
+                activate_script,
+                venv_name,
+                directories,
+            } => Some(VenvSettingsContent {
+                activate_script: activate_script.unwrap_or(ActivateScript::Default),
+                venv_name: venv_name.as_deref().unwrap_or(""),
+                directories: directories.as_deref().unwrap_or(&[]),
+            }),
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(rename_all = "snake_case")]
+pub enum TerminalDockPosition {
+    Left,
+    Bottom,
+    Right,
+}
+
+#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ActivateScript {
+    #[default]
+    Default,
+    Csh,
+    Fish,
+    Nushell,
+    PowerShell,
+    Pyenv,
+}

crates/terminal/src/terminal_settings.rs 🔗

@@ -2,22 +2,18 @@ use alacritty_terminal::vte::ansi::{
     CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
 };
 use collections::HashMap;
-use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, px};
+use gpui::{App, FontFallbacks, FontFeatures, FontWeight, Pixels, px};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 
-use settings::{SettingsKey, SettingsSources, SettingsUi};
-use std::path::PathBuf;
+pub use settings::AlternateScroll;
+use settings::{
+    CursorShapeContent, SettingsContent, ShowScrollbar, TerminalBlink, TerminalDockPosition,
+    TerminalLineHeight, TerminalSettingsContent, VenvSettings, WorkingDirectory,
+};
 use task::Shell;
 use theme::FontFamilyName;
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalDockPosition {
-    Left,
-    Bottom,
-    Right,
-}
+use util::MergeFrom;
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 pub struct Toolbar {
@@ -28,7 +24,7 @@ pub struct Toolbar {
 pub struct TerminalSettings {
     pub shell: Shell,
     pub working_directory: WorkingDirectory,
-    pub font_size: Option<Pixels>,
+    pub font_size: Option<Pixels>, // todo(settings_refactor) can be non-optional...
     pub font_family: Option<FontFamilyName>,
     pub font_fallbacks: Option<FontFallbacks>,
     pub font_features: Option<FontFeatures>,
@@ -60,218 +56,135 @@ pub struct ScrollbarSettings {
     pub show: Option<ShowScrollbar>,
 }
 
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct ScrollbarSettingsContent {
-    /// When to show the scrollbar in the terminal.
-    ///
-    /// Default: inherits editor scrollbar settings
-    pub show: Option<Option<ShowScrollbar>>,
-}
-
-/// When to show the scrollbar in the terminal.
-///
-/// Default: auto
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-#[serde(rename_all = "snake_case")]
-pub enum ShowScrollbar {
-    /// Show the scrollbar if there's important information or
-    /// follow the system's configured behavior.
-    Auto,
-    /// Match the system's configured behavior.
-    System,
-    /// Always show the scrollbar.
-    Always,
-    /// Never show the scrollbar.
-    Never,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum VenvSettings {
-    #[default]
-    Off,
-    On {
-        /// Default directories to search for virtual environments, relative
-        /// to the current working directory. We recommend overriding this
-        /// in your project's settings, rather than globally.
-        activate_script: Option<ActivateScript>,
-        venv_name: Option<String>,
-        directories: Option<Vec<PathBuf>>,
-    },
-}
-
-pub struct VenvSettingsContent<'a> {
-    pub activate_script: ActivateScript,
-    pub venv_name: &'a str,
-    pub directories: &'a [PathBuf],
+fn settings_shell_to_task_shell(shell: settings::Shell) -> Shell {
+    match shell {
+        settings::Shell::System => Shell::System,
+        settings::Shell::Program(program) => Shell::Program(program),
+        settings::Shell::WithArguments {
+            program,
+            args,
+            title_override,
+        } => Shell::WithArguments {
+            program,
+            args,
+            title_override,
+        },
+    }
 }
 
-impl VenvSettings {
-    pub fn as_option(&self) -> Option<VenvSettingsContent<'_>> {
-        match self {
-            VenvSettings::Off => None,
-            VenvSettings::On {
-                activate_script,
-                venv_name,
-                directories,
-            } => Some(VenvSettingsContent {
-                activate_script: activate_script.unwrap_or(ActivateScript::Default),
-                venv_name: venv_name.as_deref().unwrap_or(""),
-                directories: directories.as_deref().unwrap_or(&[]),
+impl settings::Settings for TerminalSettings {
+    fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
+        let content = content.terminal.clone().unwrap();
+        TerminalSettings {
+            shell: settings_shell_to_task_shell(content.shell.unwrap()),
+            working_directory: content.working_directory.unwrap(),
+            font_size: content.font_size.map(px),
+            font_family: content.font_family,
+            font_fallbacks: content.font_fallbacks.map(|fallbacks| {
+                FontFallbacks::from_fonts(
+                    fallbacks
+                        .into_iter()
+                        .map(|family| family.0.to_string())
+                        .collect(),
+                )
             }),
+            font_features: content.font_features,
+            font_weight: content.font_weight.map(FontWeight),
+            line_height: content.line_height.unwrap(),
+            env: content.env.unwrap(),
+            cursor_shape: content.cursor_shape.map(Into::into),
+            blinking: content.blinking.unwrap(),
+            alternate_scroll: content.alternate_scroll.unwrap(),
+            option_as_meta: content.option_as_meta.unwrap(),
+            copy_on_select: content.copy_on_select.unwrap(),
+            keep_selection_on_copy: content.keep_selection_on_copy.unwrap(),
+            button: content.button.unwrap(),
+            dock: content.dock.unwrap(),
+            default_width: px(content.default_width.unwrap()),
+            default_height: px(content.default_height.unwrap()),
+            detect_venv: content.detect_venv.unwrap(),
+            max_scroll_history_lines: content.max_scroll_history_lines,
+            toolbar: Toolbar {
+                breadcrumbs: content.toolbar.unwrap().breadcrumbs.unwrap(),
+            },
+            scrollbar: ScrollbarSettings {
+                show: content.scrollbar.unwrap().show.unwrap(),
+            },
+            minimum_contrast: content.minimum_contrast.unwrap(),
         }
     }
-}
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum ActivateScript {
-    #[default]
-    Default,
-    Csh,
-    Fish,
-    Nushell,
-    PowerShell,
-    Pyenv,
-}
 
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
-#[settings_key(key = "terminal")]
-pub struct TerminalSettingsContent {
-    /// What shell to use when opening a terminal.
-    ///
-    /// Default: system
-    pub shell: Option<Shell>,
-    /// What working directory to use when launching the terminal
-    ///
-    /// Default: current_project_directory
-    pub working_directory: Option<WorkingDirectory>,
-    /// Sets the terminal's font size.
-    ///
-    /// If this option is not included,
-    /// the terminal will default to matching the buffer's font size.
-    pub font_size: Option<f32>,
-    /// Sets the terminal's font family.
-    ///
-    /// If this option is not included,
-    /// the terminal will default to matching the buffer's font family.
-    pub font_family: Option<FontFamilyName>,
-
-    /// Sets the terminal's font fallbacks.
-    ///
-    /// If this option is not included,
-    /// the terminal will default to matching the buffer's font fallbacks.
-    #[schemars(extend("uniqueItems" = true))]
-    pub font_fallbacks: Option<Vec<FontFamilyName>>,
-
-    /// Sets the terminal's line height.
-    ///
-    /// Default: comfortable
-    pub line_height: Option<TerminalLineHeight>,
-    pub font_features: Option<FontFeatures>,
-    /// Sets the terminal's font weight in CSS weight units 0-900.
-    pub font_weight: Option<f32>,
-    /// Any key-value pairs added to this list will be added to the terminal's
-    /// environment. Use `:` to separate multiple values.
-    ///
-    /// Default: {}
-    pub env: Option<HashMap<String, String>>,
-    /// Default cursor shape for the terminal.
-    /// Can be "bar", "block", "underline", or "hollow".
-    ///
-    /// Default: None
-    pub cursor_shape: Option<CursorShape>,
-    /// Sets the cursor blinking behavior in the terminal.
-    ///
-    /// Default: terminal_controlled
-    pub blinking: Option<TerminalBlink>,
-    /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
-    /// Alternate Scroll mode converts mouse scroll events into up / down key
-    /// presses when in the alternate screen (e.g. when running applications
-    /// like vim or  less). The terminal can still set and unset this mode.
-    ///
-    /// Default: on
-    pub alternate_scroll: Option<AlternateScroll>,
-    /// Sets whether the option key behaves as the meta key.
-    ///
-    /// Default: false
-    pub option_as_meta: Option<bool>,
-    /// Whether or not selecting text in the terminal will automatically
-    /// copy to the system clipboard.
-    ///
-    /// Default: false
-    pub copy_on_select: Option<bool>,
-    /// Whether to keep the text selection after copying it to the clipboard.
-    ///
-    /// Default: false
-    pub keep_selection_on_copy: Option<bool>,
-    /// Whether to show the terminal button in the status bar.
-    ///
-    /// Default: true
-    pub button: Option<bool>,
-    pub dock: Option<TerminalDockPosition>,
-    /// Default width when the terminal is docked to the left or right.
-    ///
-    /// Default: 640
-    pub default_width: Option<f32>,
-    /// Default height when the terminal is docked to the bottom.
-    ///
-    /// Default: 320
-    pub default_height: Option<f32>,
-    /// Activates the python virtual environment, if one is found, in the
-    /// terminal's working directory (as resolved by the working_directory
-    /// setting). Set this to "off" to disable this behavior.
-    ///
-    /// Default: on
-    pub detect_venv: Option<VenvSettings>,
-    /// The maximum number of lines to keep in the scrollback history.
-    /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
-    /// 0 disables the scrolling.
-    /// Existing terminals will not pick up this change until they are recreated.
-    /// See <a href="https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213">Alacritty documentation</a> for more information.
-    ///
-    /// Default: 10_000
-    pub max_scroll_history_lines: Option<usize>,
-    /// Toolbar related settings
-    pub toolbar: Option<ToolbarContent>,
-    /// Scrollbar-related settings
-    pub scrollbar: Option<ScrollbarSettingsContent>,
-    /// The minimum APCA perceptual contrast between foreground and background colors.
-    ///
-    /// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
-    /// especially for dark mode. Values range from 0 to 106.
-    ///
-    /// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
-    /// https://readtech.org/ARC/tests/bronze-simple-mode/
-    /// - 0: No contrast adjustment
-    /// - 45: Minimum for large fluent text (36px+)
-    /// - 60: Minimum for other content text
-    /// - 75: Minimum for body text
-    /// - 90: Preferred for body text
-    ///
-    /// Default: 45
-    pub minimum_contrast: Option<f32>,
-}
-
-impl settings::Settings for TerminalSettings {
-    type FileContent = TerminalSettingsContent;
-
-    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
-        let settings: Self = sources.json_merge()?;
-
-        // Validate minimum_contrast for APCA
-        if settings.minimum_contrast < 0.0 || settings.minimum_contrast > 106.0 {
-            anyhow::bail!(
-                "terminal.minimum_contrast must be between 0 and 106, but got {}. \
-                APCA values: 0 = no adjustment, 75 = recommended for body text, 106 = maximum contrast.",
-                settings.minimum_contrast
-            );
+    fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
+        let Some(content) = &content.terminal else {
+            return;
+        };
+        self.shell
+            .merge_from(&content.shell.clone().map(settings_shell_to_task_shell));
+        self.working_directory
+            .merge_from(&content.working_directory);
+        if let Some(font_size) = content.font_size.map(px) {
+            self.font_size = Some(font_size)
         }
-
-        Ok(settings)
+        if let Some(font_family) = content.font_family.clone() {
+            self.font_family = Some(font_family);
+        }
+        if let Some(fallbacks) = content.font_fallbacks.clone() {
+            self.font_fallbacks = Some(FontFallbacks::from_fonts(
+                fallbacks
+                    .into_iter()
+                    .map(|family| family.0.to_string())
+                    .collect(),
+            ))
+        }
+        if let Some(font_features) = content.font_features.clone() {
+            self.font_features = Some(font_features)
+        }
+        if let Some(font_weight) = content.font_weight {
+            self.font_weight = Some(FontWeight(font_weight));
+        }
+        self.line_height.merge_from(&content.line_height);
+        if let Some(env) = &content.env {
+            for (key, value) in env {
+                self.env.insert(key.clone(), value.clone());
+            }
+        }
+        if let Some(cursor_shape) = content.cursor_shape {
+            self.cursor_shape = Some(cursor_shape.into())
+        }
+        self.blinking.merge_from(&content.blinking);
+        self.alternate_scroll.merge_from(&content.alternate_scroll);
+        self.option_as_meta.merge_from(&content.option_as_meta);
+        self.copy_on_select.merge_from(&content.copy_on_select);
+        self.keep_selection_on_copy
+            .merge_from(&content.keep_selection_on_copy);
+        self.button.merge_from(&content.button);
+        self.dock.merge_from(&content.dock);
+        self.default_width
+            .merge_from(&content.default_width.map(px));
+        self.default_height
+            .merge_from(&content.default_height.map(px));
+        self.detect_venv.merge_from(&content.detect_venv);
+        if let Some(max_scroll_history_lines) = content.max_scroll_history_lines {
+            self.max_scroll_history_lines = Some(max_scroll_history_lines)
+        }
+        self.toolbar.breadcrumbs.merge_from(
+            &content
+                .toolbar
+                .as_ref()
+                .and_then(|toolbar| toolbar.breadcrumbs),
+        );
+        self.scrollbar.show.merge_from(
+            &content
+                .scrollbar
+                .as_ref()
+                .and_then(|scrollbar| scrollbar.show),
+        );
+        self.minimum_contrast.merge_from(&content.minimum_contrast);
     }
 
-    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
+    fn import_from_vscode(vscode: &settings::VsCodeSettings, content: &mut SettingsContent) {
+        let mut default = TerminalSettingsContent::default();
+        let current = content.terminal.as_mut().unwrap_or(&mut default);
         let name = |s| format!("terminal.integrated.{s}");
 
         vscode.f32_setting(&name("fontSize"), &mut current.font_size);
@@ -290,9 +203,9 @@ impl settings::Settings for TerminalSettings {
             &name("cursorStyle"),
             &mut current.cursor_shape,
             |s| match s {
-                "block" => Some(CursorShape::Block),
-                "line" => Some(CursorShape::Bar),
-                "underline" => Some(CursorShape::Underline),
+                "block" => Some(CursorShapeContent::Block),
+                "line" => Some(CursorShapeContent::Bar),
+                "underline" => Some(CursorShapeContent::Underline),
                 _ => None,
             },
         );
@@ -316,7 +229,7 @@ impl settings::Settings for TerminalSettings {
         // TODO: handle arguments
         let shell_name = format!("{platform}Exec");
         if let Some(s) = vscode.read_string(&name(&shell_name)) {
-            current.shell = Some(Shell::Program(s.to_owned()))
+            current.shell = Some(settings::Shell::Program(s.to_owned()))
         }
 
         if let Some(env) = vscode
@@ -337,81 +250,13 @@ impl settings::Settings for TerminalSettings {
                 }
             }
         }
+        // todo!() test that this works.
+        if content.terminal.is_none() && default != TerminalSettingsContent::default() {
+            content.terminal = Some(default)
+        }
     }
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalLineHeight {
-    /// Use a line height that's comfortable for reading, 1.618
-    #[default]
-    Comfortable,
-    /// Use a standard line height, 1.3. This option is useful for TUIs,
-    /// particularly if they use box characters
-    Standard,
-    /// Use a custom line height.
-    Custom(f32),
-}
-
-impl TerminalLineHeight {
-    pub fn value(&self) -> AbsoluteLength {
-        let value = match self {
-            TerminalLineHeight::Comfortable => 1.618,
-            TerminalLineHeight::Standard => 1.3,
-            TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
-        };
-        px(value).into()
-    }
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum TerminalBlink {
-    /// Never blink the cursor, ignoring the terminal mode.
-    Off,
-    /// Default the cursor blink to off, but allow the terminal to
-    /// set blinking.
-    TerminalControlled,
-    /// Always blink the cursor, ignoring the terminal mode.
-    On,
-}
-
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum AlternateScroll {
-    On,
-    Off,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum WorkingDirectory {
-    /// Use the current file's project directory.  Will Fallback to the
-    /// first project directory strategy if unsuccessful.
-    CurrentProjectDirectory,
-    /// Use the first project in this workspace's directory.
-    FirstProjectDirectory,
-    /// Always use this platform's home directory (if it can be found).
-    AlwaysHome,
-    /// Always use a specific directory. This value will be shell expanded.
-    /// If this path is not a valid directory the terminal will default to
-    /// this platform's home directory  (if it can be found).
-    Always { directory: String },
-}
-
-// Toolbar related settings
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct ToolbarContent {
-    /// Whether to display the terminal title in breadcrumbs inside the terminal pane.
-    /// Only shown if the terminal title is not empty.
-    ///
-    /// The shell running in the terminal needs to be configured to emit the title.
-    /// Example: `echo -e "\e]2;New Title\007";`
-    ///
-    /// Default: true
-    pub breadcrumbs: Option<bool>,
-}
-
 #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum CursorShape {
@@ -426,6 +271,17 @@ pub enum CursorShape {
     Hollow,
 }
 
+impl From<settings::CursorShapeContent> for CursorShape {
+    fn from(value: settings::CursorShapeContent) -> Self {
+        match value {
+            settings::CursorShapeContent::Block => CursorShape::Block,
+            settings::CursorShapeContent::Underline => CursorShape::Underline,
+            settings::CursorShapeContent::Bar => CursorShape::Bar,
+            settings::CursorShapeContent::Hollow => CursorShape::Hollow,
+        }
+    }
+}
+
 impl From<CursorShape> for AlacCursorShape {
     fn from(value: CursorShape) -> Self {
         match value {

crates/theme/src/settings.rs 🔗

@@ -13,10 +13,8 @@ use gpui::{
 use refineable::Refineable;
 use schemars::{JsonSchema, json_schema};
 use serde::{Deserialize, Serialize};
-use settings::{
-    FontFamilyName, IconThemeName, ParameterizedJsonSchema, Settings, SettingsContent, ThemeMode,
-    ThemeName,
-};
+pub use settings::{FontFamilyName, IconThemeName, ThemeMode, ThemeName};
+use settings::{ParameterizedJsonSchema, Settings, SettingsContent};
 use std::sync::Arc;
 use util::schemars::replace_subschema;
 use util::{MergeFrom, ResultExt as _};
@@ -782,7 +780,8 @@ fn clamp_font_weight(weight: f32) -> FontWeight {
     FontWeight(weight.clamp(100., 950.))
 }
 
-fn font_fallbacks_from_settings(
+/// font fallback from settings
+pub fn font_fallbacks_from_settings(
     fallbacks: Option<Vec<settings::FontFamilyName>>,
 ) -> Option<FontFallbacks> {
     fallbacks.map(|fallbacks| {