Create a new setting to adjust the line height in the terminal

Mikayla Maki created

Change summary

assets/settings/default.json                 | 12 ++
crates/settings/src/settings.rs              | 92 ++++++++++++++++++---
crates/terminal_view/src/terminal_element.rs |  2 
3 files changed, 89 insertions(+), 17 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -197,7 +197,17 @@
         // enviroment. Use `:` to seperate multiple values.
         "env": {
             // "KEY": "value1:value2"
-        }
+        },
+        // Set the terminal's line height.
+        // May take 3 values:
+        //  1. Use a line height that's comfortable for reading, 1.618
+        //         "line_height": "comfortable"
+        //  2. Use a standard line height, 1.3. This option is useful for TUIs,
+        //      particularly if they use box characters
+        //         "line_height": "standard",
+        //  3. Use a custom line height.
+        //         "line_height": 1.2,
+        "line_height": "comfortable"
         // Set the terminal's font size. If this option is not included,
         // the terminal will default to matching the buffer's font size.
         // "font_size": "15"

crates/settings/src/settings.rs 🔗

@@ -12,7 +12,7 @@ use schemars::{
     schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
     JsonSchema,
 };
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
+use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize};
 use serde_json::Value;
 use sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
@@ -252,6 +252,7 @@ pub struct TerminalSettings {
     pub working_directory: Option<WorkingDirectory>,
     pub font_size: Option<f32>,
     pub font_family: Option<String>,
+    pub line_height: Option<TerminalLineHeight>,
     pub font_features: Option<fonts::Features>,
     pub env: Option<HashMap<String, String>>,
     pub blinking: Option<TerminalBlink>,
@@ -260,6 +261,56 @@ pub struct TerminalSettings {
     pub copy_on_select: Option<bool>,
 }
 
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
+#[serde(rename_all = "snake_case")]
+#[serde(untagged)]
+pub enum TerminalLineHeight {
+    #[default]
+    #[serde(deserialize_with = "comfortable")]
+    Comfortable,
+    #[serde(deserialize_with = "standard")]
+    Standard,
+    Custom(f32),
+}
+
+// Copied from: https://github.com/serde-rs/serde/issues/1158#issuecomment-365362959
+fn comfortable<'de, D>(deserializer: D) -> Result<(), D::Error>
+where
+    D: Deserializer<'de>,
+{
+    #[derive(Deserialize)]
+    enum Helper {
+        #[serde(rename = "comfortable")]
+        Variant,
+    }
+    Helper::deserialize(deserializer)?;
+    Ok(())
+}
+
+// Copied from: https://github.com/serde-rs/serde/issues/1158#issuecomment-365362959
+fn standard<'de, D>(deserializer: D) -> Result<(), D::Error>
+where
+    D: Deserializer<'de>,
+{
+    #[derive(Deserialize)]
+    enum Helper {
+        #[serde(rename = "standard")]
+        Variant,
+    }
+    Helper::deserialize(deserializer)?;
+    Ok(())
+}
+
+impl TerminalLineHeight {
+    fn value(&self) -> f32 {
+        match self {
+            TerminalLineHeight::Comfortable => 1.618,
+            TerminalLineHeight::Standard => 1.3,
+            TerminalLineHeight::Custom(line_height) => *line_height,
+        }
+    }
+}
+
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum TerminalBlink {
@@ -316,6 +367,14 @@ impl Default for WorkingDirectory {
     }
 }
 
+impl TerminalSettings {
+    fn line_height(&self) -> Option<f32> {
+        self.line_height
+            .to_owned()
+            .map(|line_height| line_height.value())
+    }
+}
+
 #[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Hash, Serialize, Deserialize, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum DockAnchor {
@@ -640,16 +699,6 @@ impl Settings {
         })
     }
 
-    fn terminal_setting<F, R: Default + Clone>(&self, f: F) -> R
-    where
-        F: Fn(&TerminalSettings) -> Option<&R>,
-    {
-        f(&self.terminal_overrides)
-            .or_else(|| f(&self.terminal_defaults))
-            .cloned()
-            .unwrap_or_else(|| R::default())
-    }
-
     pub fn telemetry(&self) -> TelemetrySettings {
         TelemetrySettings {
             diagnostics: Some(self.telemetry_diagnostics()),
@@ -671,20 +720,33 @@ impl Settings {
             .expect("missing default")
     }
 
+    fn terminal_setting<F, R>(&self, f: F) -> R
+    where
+        F: Fn(&TerminalSettings) -> Option<R>,
+    {
+        None.or_else(|| f(&self.terminal_overrides))
+            .or_else(|| f(&self.terminal_defaults))
+            .expect("missing default")
+    }
+
+    pub fn terminal_line_height(&self) -> f32 {
+        self.terminal_setting(|terminal_setting| terminal_setting.line_height())
+    }
+
     pub fn terminal_scroll(&self) -> AlternateScroll {
-        self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.as_ref())
+        self.terminal_setting(|terminal_setting| terminal_setting.alternate_scroll.to_owned())
     }
 
     pub fn terminal_shell(&self) -> Shell {
-        self.terminal_setting(|terminal_setting| terminal_setting.shell.as_ref())
+        self.terminal_setting(|terminal_setting| terminal_setting.shell.to_owned())
     }
 
     pub fn terminal_env(&self) -> HashMap<String, String> {
-        self.terminal_setting(|terminal_setting| terminal_setting.env.as_ref())
+        self.terminal_setting(|terminal_setting| terminal_setting.env.to_owned())
     }
 
     pub fn terminal_strategy(&self) -> WorkingDirectory {
-        self.terminal_setting(|terminal_setting| terminal_setting.working_directory.as_ref())
+        self.terminal_setting(|terminal_setting| terminal_setting.working_directory.to_owned())
     }
 
     #[cfg(any(test, feature = "test-support"))]

crates/terminal_view/src/terminal_element.rs 🔗

@@ -567,7 +567,7 @@ impl Element for TerminalElement {
         let selection_color = settings.theme.editor.selection.selection;
         let match_color = settings.theme.search.match_background;
         let dimensions = {
-            let line_height = font_cache.line_height(text_style.font_size);
+            let line_height = text_style.font_size * settings.terminal_line_height();
             let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
             TerminalSize::new(line_height, cell_width, constraint.max)
         };