Merge pull request #1376 from zed-industries/terminal-settings

Mikayla Maki created

Added settings for common terminal configurations

Change summary

Cargo.lock                                         |   1 
assets/settings/default.json                       |  50 ++++
crates/settings/src/settings.rs                    |  51 ++++
crates/terminal/Cargo.toml                         |   1 
crates/terminal/src/connection.rs                  |  43 +++
crates/terminal/src/terminal.rs                    | 178 +++++++++------
crates/terminal/src/terminal_element.rs            |  35 ++
crates/terminal/src/tests/terminal_test_context.rs |  12 
crates/zed/src/settings_file.rs                    |  19 +
crates/zed/src/zed.rs                              |   7 
10 files changed, 296 insertions(+), 101 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5365,6 +5365,7 @@ dependencies = [
  "ordered-float",
  "project",
  "settings",
+ "shellexpand",
  "smallvec",
  "theme",
  "util",

assets/settings/default.json 🔗

@@ -54,7 +54,7 @@
     //      "soft_wrap": "none",
     // 2. Soft wrap lines that overflow the editor:
     //      "soft_wrap": "editor_width",
-    // 2. Soft wrap lines at the preferred line length
+    // 3. Soft wrap lines at the preferred line length
     //      "soft_wrap": "preferred_line_length",
     "soft_wrap": "none",
     // The column at which to soft-wrap lines, for buffers where soft-wrap
@@ -65,6 +65,54 @@
     "hard_tabs": false,
     // How many columns a tab should occupy.
     "tab_size": 4,
+    // Settings specific to the terminal
+    "terminal": {
+        // What shell to use when opening a terminal. May take 3 values: 
+        // 1. Use the system's default terminal configuration (e.g. $TERM).
+        //      "shell": "system"
+        // 2. A program:
+        //      "shell": {
+        //        "program": "sh"
+        //      }
+        // 3. A program with arguments:
+        //     "shell": {
+        //         "with_arguments": {
+        //           "program": "/bin/bash",
+        //           "arguments": ["--login"]
+        //         }
+        //     }
+        "shell": "system",
+        // What working directory to use when launching the terminal.
+        // May take 4 values:
+        // 1. Use the current file's project directory.
+        //      "working_directory": "current_project_directory"
+        // 2. Use the first project in this workspace's directory
+        //      "working_directory": "first_project_directory"
+        // 3. Always use this platform's home directory (if we can find it)
+        //     "working_directory": "always_home"
+        // 4. 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 we can find it)
+        //      "working_directory": { 
+        //        "always": {
+        //          "directory": "~/zed/projects/"
+        //        }
+        //      }
+        //
+        //
+        "working_directory": "current_project_directory",
+        //Any key-value pairs added to this list will be added to the terminal's
+        //enviroment. Use `:` to seperate multiple values, not multiple list items
+        "env": [
+            //["KEY", "value1:value2"]
+        ]
+        //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"
+        //Set the terminal's font family. If this option is not included,
+        //the terminal will default to matching the buffer's font family.
+        //"font_family": "Zed Mono"
+    },
     // Different settings for specific languages.
     "languages": {
         "Plain Text": {

crates/settings/src/settings.rs 🔗

@@ -22,14 +22,16 @@ pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
 pub struct Settings {
     pub projects_online_by_default: bool,
     pub buffer_font_family: FamilyId,
-    pub buffer_font_size: f32,
     pub default_buffer_font_size: f32,
+    pub buffer_font_size: f32,
     pub hover_popover_enabled: bool,
     pub show_completions_on_input: bool,
     pub vim_mode: bool,
     pub autosave: Autosave,
     pub editor_defaults: EditorSettings,
     pub editor_overrides: EditorSettings,
+    pub terminal_defaults: TerminalSettings,
+    pub terminal_overrides: TerminalSettings,
     pub language_defaults: HashMap<Arc<str>, EditorSettings>,
     pub language_overrides: HashMap<Arc<str>, EditorSettings>,
     pub theme: Arc<Theme>,
@@ -73,6 +75,38 @@ pub enum Autosave {
     OnWindowChange,
 }
 
+#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
+pub struct TerminalSettings {
+    pub shell: Option<Shell>,
+    pub working_directory: Option<WorkingDirectory>,
+    pub font_size: Option<f32>,
+    pub font_family: Option<String>,
+    pub env: Option<Vec<(String, String)>>,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum Shell {
+    System,
+    Program(String),
+    WithArguments { program: String, args: Vec<String> },
+}
+
+impl Default for Shell {
+    fn default() -> Self {
+        Shell::System
+    }
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum WorkingDirectory {
+    CurrentProjectDirectory,
+    FirstProjectDirectory,
+    AlwaysHome,
+    Always { directory: String },
+}
+
 #[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 pub struct SettingsFileContent {
     #[serde(default)]
@@ -92,6 +126,8 @@ pub struct SettingsFileContent {
     #[serde(flatten)]
     pub editor: EditorSettings,
     #[serde(default)]
+    pub terminal: TerminalSettings,
+    #[serde(default)]
     #[serde(alias = "language_overrides")]
     pub languages: HashMap<Arc<str>, EditorSettings>,
     #[serde(default)]
@@ -133,8 +169,10 @@ impl Settings {
                 format_on_save: required(defaults.editor.format_on_save),
                 enable_language_server: required(defaults.editor.enable_language_server),
             },
-            language_defaults: defaults.languages,
             editor_overrides: Default::default(),
+            terminal_defaults: Default::default(),
+            terminal_overrides: Default::default(),
+            language_defaults: defaults.languages,
             language_overrides: Default::default(),
             theme: themes.get(&defaults.theme.unwrap()).unwrap(),
         }
@@ -171,7 +209,14 @@ impl Settings {
         merge(&mut self.vim_mode, data.vim_mode);
         merge(&mut self.autosave, data.autosave);
 
+        // Ensure terminal font is loaded, so we can request it in terminal_element layout
+        if let Some(terminal_font) = &data.terminal.font_family {
+            font_cache.load_family(&[terminal_font]).log_err();
+        }
+
         self.editor_overrides = data.editor;
+        self.terminal_defaults.font_size = data.terminal.font_size;
+        self.terminal_overrides = data.terminal;
         self.language_overrides = data.languages;
     }
 
@@ -239,6 +284,8 @@ impl Settings {
                 enable_language_server: Some(true),
             },
             editor_overrides: Default::default(),
+            terminal_defaults: Default::default(),
+            terminal_overrides: Default::default(),
             language_defaults: Default::default(),
             language_overrides: Default::default(),
             projects_online_by_default: true,

crates/terminal/Cargo.toml 🔗

@@ -22,6 +22,7 @@ futures = "0.3"
 ordered-float = "2.1.1"
 itertools = "0.10"
 dirs = "4.0.0"
+shellexpand = "2.1.0"
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }

crates/terminal/src/connection.rs 🔗

@@ -2,7 +2,7 @@ mod keymappings;
 
 use alacritty_terminal::{
     ansi::{ClearMode, Handler},
-    config::{Config, PtyConfig},
+    config::{Config, Program, PtyConfig},
     event::{Event as AlacTermEvent, Notify},
     event_loop::{EventLoop, Msg, Notifier},
     grid::Scroll,
@@ -12,7 +12,7 @@ use alacritty_terminal::{
     Term,
 };
 use futures::{channel::mpsc::unbounded, StreamExt};
-use settings::Settings;
+use settings::{Settings, Shell};
 use std::{collections::HashMap, path::PathBuf, sync::Arc};
 
 use gpui::{keymap::Keystroke, ClipboardItem, CursorStyle, Entity, ModelContext};
@@ -46,16 +46,32 @@ pub struct TerminalConnection {
 impl TerminalConnection {
     pub fn new(
         working_directory: Option<PathBuf>,
+        shell: Option<Shell>,
+        env_vars: Option<Vec<(String, String)>>,
         initial_size: SizeInfo,
         cx: &mut ModelContext<Self>,
     ) -> TerminalConnection {
-        let pty_config = PtyConfig {
-            shell: None, //Use the users default shell
-            working_directory: working_directory.clone(),
-            hold: false,
+        let pty_config = {
+            let shell = shell.and_then(|shell| match shell {
+                Shell::System => None,
+                Shell::Program(program) => Some(Program::Just(program)),
+                Shell::WithArguments { program, args } => Some(Program::WithArgs { program, args }),
+            });
+
+            PtyConfig {
+                shell,
+                working_directory: working_directory.clone(),
+                hold: false,
+            }
         };
 
         let mut env: HashMap<String, String> = HashMap::new();
+        if let Some(envs) = env_vars {
+            for (var, val) in envs {
+                env.insert(var, val);
+            }
+        }
+
         //TODO: Properly set the current locale,
         env.insert("LC_ALL".to_string(), "en_US.UTF-8".to_string());
 
@@ -75,7 +91,20 @@ impl TerminalConnection {
         let term = Arc::new(FairMutex::new(term));
 
         //Setup the pty...
-        let pty = tty::new(&pty_config, &initial_size, None).expect("Could not create tty");
+        let pty = {
+            if let Some(pty) = tty::new(&pty_config, &initial_size, None).ok() {
+                pty
+            } else {
+                let pty_config = PtyConfig {
+                    shell: None,
+                    working_directory: working_directory.clone(),
+                    ..Default::default()
+                };
+
+                tty::new(&pty_config, &initial_size, None)
+                    .expect("Failed with default shell too :(")
+            }
+        };
 
         //And connect them together
         let event_loop = EventLoop::new(

crates/terminal/src/terminal.rs 🔗

@@ -18,10 +18,10 @@ use gpui::{
 };
 use modal::deploy_modal;
 
-use project::{Project, ProjectPath};
-use settings::Settings;
+use project::{LocalWorktree, Project, ProjectPath};
+use settings::{Settings, WorkingDirectory};
 use smallvec::SmallVec;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use workspace::{Item, Workspace};
 
 use crate::terminal_element::TerminalEl;
@@ -110,8 +110,15 @@ impl Terminal {
             false,
         );
 
-        let connection =
-            cx.add_model(|cx| TerminalConnection::new(working_directory, size_info, cx));
+        let (shell, envs) = {
+            let settings = cx.global::<Settings>();
+            let shell = settings.terminal_overrides.shell.clone();
+            let envs = settings.terminal_overrides.env.clone(); //Should be short and cheap.
+            (shell, envs)
+        };
+
+        let connection = cx
+            .add_model(|cx| TerminalConnection::new(working_directory, shell, envs, size_info, cx));
 
         Terminal::from_connection(connection, modal, cx)
     }
@@ -371,12 +378,41 @@ impl Item for Terminal {
     }
 }
 
+///Get's the working directory for the given workspace, respecting the user's settings.
+fn get_wd_for_workspace(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
+    let wd_setting = cx
+        .global::<Settings>()
+        .terminal_overrides
+        .working_directory
+        .clone()
+        .unwrap_or(WorkingDirectory::CurrentProjectDirectory);
+    let res = match wd_setting {
+        WorkingDirectory::CurrentProjectDirectory => current_project_directory(workspace, cx),
+        WorkingDirectory::FirstProjectDirectory => first_project_directory(workspace, cx),
+        WorkingDirectory::AlwaysHome => None,
+        WorkingDirectory::Always { directory } => shellexpand::full(&directory)
+            .ok()
+            .map(|dir| Path::new(&dir.to_string()).to_path_buf())
+            .filter(|dir| dir.is_dir()),
+    };
+    res.or_else(|| home_dir())
+}
+
+///Get's the first project's home directory, or the home directory
+fn first_project_directory(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
+    workspace
+        .worktrees(cx)
+        .next()
+        .and_then(|worktree_handle| worktree_handle.read(cx).as_local())
+        .and_then(get_path_from_wt)
+}
+
 ///Gets the intuitively correct working directory from the given workspace
 ///If there is an active entry for this project, returns that entry's worktree root.
 ///If there's no active entry but there is a worktree, returns that worktrees root.
 ///If either of these roots are files, or if there are any other query failures,
 ///  returns the user's home directory
-fn get_wd_for_workspace(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
+fn current_project_directory(workspace: &Workspace, cx: &AppContext) -> Option<PathBuf> {
     let project = workspace.project().read(cx);
 
     project
@@ -384,12 +420,13 @@ fn get_wd_for_workspace(workspace: &Workspace, cx: &AppContext) -> Option<PathBu
         .and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
         .or_else(|| workspace.worktrees(cx).next())
         .and_then(|worktree_handle| worktree_handle.read(cx).as_local())
-        .and_then(|wt| {
-            wt.root_entry()
-                .filter(|re| re.is_dir())
-                .map(|_| wt.abs_path().to_path_buf())
-        })
-        .or_else(|| home_dir())
+        .and_then(get_path_from_wt)
+}
+
+fn get_path_from_wt(wt: &LocalWorktree) -> Option<PathBuf> {
+    wt.root_entry()
+        .filter(|re| re.is_dir())
+        .map(|_| wt.abs_path().to_path_buf())
 }
 
 #[cfg(test)]
@@ -398,10 +435,6 @@ mod tests {
     use crate::tests::terminal_test_context::TerminalTestContext;
 
     use super::*;
-    use alacritty_terminal::{
-        index::{Column, Line, Point, Side},
-        selection::{Selection, SelectionType},
-    };
     use gpui::TestAppContext;
 
     use std::path::Path;
@@ -419,37 +452,6 @@ mod tests {
             .await;
     }
 
-    /// TODO: I don't think this is actually testing anything anymore.
-    ///Integration test for selections, clipboard, and terminal execution
-    #[gpui::test(retries = 5)]
-    async fn test_copy(cx: &mut TestAppContext) {
-
-        let mut cx = TerminalTestContext::new(cx);
-        let grid_content = cx
-            .execute_and_wait("expr 3 + 4", |content, _cx| content.contains("7"))
-            .await;
-
-        //Get the position of the result
-        let idx = grid_content.chars().position(|c| c == '7').unwrap();
-        let result_line = grid_content
-            .chars()
-            .take(idx)
-            .filter(|c| *c == '\n')
-            .count() as i32;
-
-        let copy_res = cx.update_connection(|connection, _cx| {
-            let mut term = connection.term.lock();
-            term.selection = Some(Selection::new(
-                SelectionType::Semantic,
-                Point::new(Line(result_line), Column(0)),
-                Side::Right,
-            ));
-            term.selection_to_string()
-        });
-
-        assert_eq!(copy_res.unwrap(), "7");
-    }
-
     ///Working directory calculation tests
 
     ///No Worktrees in project -> home_dir()
@@ -469,8 +471,10 @@ mod tests {
             assert!(active_entry.is_none());
             assert!(workspace.worktrees(cx).next().is_none());
 
-            let res = get_wd_for_workspace(workspace, cx);
-            assert_eq!(res, home_dir())
+            let res = current_project_directory(workspace, cx);
+            assert_eq!(res, None);
+            let res = first_project_directory(workspace, cx);
+            assert_eq!(res, None);
         });
     }
 
@@ -507,8 +511,10 @@ mod tests {
             assert!(active_entry.is_none());
             assert!(workspace.worktrees(cx).next().is_some());
 
-            let res = get_wd_for_workspace(workspace, cx);
-            assert_eq!(res, home_dir())
+            let res = current_project_directory(workspace, cx);
+            assert_eq!(res, None);
+            let res = first_project_directory(workspace, cx);
+            assert_eq!(res, None);
         });
     }
 
@@ -543,7 +549,9 @@ mod tests {
             assert!(active_entry.is_none());
             assert!(workspace.worktrees(cx).next().is_some());
 
-            let res = get_wd_for_workspace(workspace, cx);
+            let res = current_project_directory(workspace, cx);
+            assert_eq!(res, Some((Path::new("/root/")).to_path_buf()));
+            let res = first_project_directory(workspace, cx);
             assert_eq!(res, Some((Path::new("/root/")).to_path_buf()));
         });
     }
@@ -555,17 +563,32 @@ mod tests {
         let params = cx.update(AppState::test);
         let project = Project::test(params.fs.clone(), [], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
-        let (wt, _) = project
+        let (wt1, _) = project
             .update(cx, |project, cx| {
-                project.find_or_create_local_worktree("/root.txt", true, cx)
+                project.find_or_create_local_worktree("/root1/", true, cx)
+            })
+            .await
+            .unwrap();
+
+        let (wt2, _) = project
+            .update(cx, |project, cx| {
+                project.find_or_create_local_worktree("/root2.txt", true, cx)
             })
             .await
             .unwrap();
 
         //Setup root
-        let entry = cx
+        let _ = cx
             .update(|cx| {
-                wt.update(cx, |wt, cx| {
+                wt1.update(cx, |wt, cx| {
+                    wt.as_local().unwrap().create_entry(Path::new(""), true, cx)
+                })
+            })
+            .await
+            .unwrap();
+        let entry2 = cx
+            .update(|cx| {
+                wt2.update(cx, |wt, cx| {
                     wt.as_local()
                         .unwrap()
                         .create_entry(Path::new(""), false, cx)
@@ -576,8 +599,8 @@ mod tests {
 
         cx.update(|cx| {
             let p = ProjectPath {
-                worktree_id: wt.read(cx).id(),
-                path: entry.path,
+                worktree_id: wt2.read(cx).id(),
+                path: entry2.path,
             };
             project.update(cx, |project, cx| project.set_active_path(Some(p), cx));
         });
@@ -589,8 +612,10 @@ mod tests {
 
             assert!(active_entry.is_some());
 
-            let res = get_wd_for_workspace(workspace, cx);
-            assert_eq!(res, home_dir());
+            let res = current_project_directory(workspace, cx);
+            assert_eq!(res, None);
+            let res = first_project_directory(workspace, cx);
+            assert_eq!(res, Some((Path::new("/root1/")).to_path_buf()));
         });
     }
 
@@ -601,17 +626,32 @@ mod tests {
         let params = cx.update(AppState::test);
         let project = Project::test(params.fs.clone(), [], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx));
-        let (wt, _) = project
+        let (wt1, _) = project
             .update(cx, |project, cx| {
-                project.find_or_create_local_worktree("/root/", true, cx)
+                project.find_or_create_local_worktree("/root1/", true, cx)
+            })
+            .await
+            .unwrap();
+
+        let (wt2, _) = project
+            .update(cx, |project, cx| {
+                project.find_or_create_local_worktree("/root2/", true, cx)
             })
             .await
             .unwrap();
 
         //Setup root
-        let entry = cx
+        let _ = cx
             .update(|cx| {
-                wt.update(cx, |wt, cx| {
+                wt1.update(cx, |wt, cx| {
+                    wt.as_local().unwrap().create_entry(Path::new(""), true, cx)
+                })
+            })
+            .await
+            .unwrap();
+        let entry2 = cx
+            .update(|cx| {
+                wt2.update(cx, |wt, cx| {
                     wt.as_local().unwrap().create_entry(Path::new(""), true, cx)
                 })
             })
@@ -620,8 +660,8 @@ mod tests {
 
         cx.update(|cx| {
             let p = ProjectPath {
-                worktree_id: wt.read(cx).id(),
-                path: entry.path,
+                worktree_id: wt2.read(cx).id(),
+                path: entry2.path,
             };
             project.update(cx, |project, cx| project.set_active_path(Some(p), cx));
         });
@@ -633,8 +673,10 @@ mod tests {
 
             assert!(active_entry.is_some());
 
-            let res = get_wd_for_workspace(workspace, cx);
-            assert_eq!(res, Some((Path::new("/root/")).to_path_buf()));
+            let res = current_project_directory(workspace, cx);
+            assert_eq!(res, Some((Path::new("/root2/")).to_path_buf()));
+            let res = first_project_directory(workspace, cx);
+            assert_eq!(res, Some((Path::new("/root1/")).to_path_buf()));
         });
     }
 }

crates/terminal/src/terminal_element.rs 🔗

@@ -27,6 +27,7 @@ use itertools::Itertools;
 use ordered_float::OrderedFloat;
 use settings::Settings;
 use theme::TerminalStyle;
+use util::ResultExt;
 
 use std::{cmp::min, ops::Range, rc::Rc, sync::Arc};
 use std::{fmt::Debug, ops::Sub};
@@ -256,10 +257,11 @@ impl Element for TerminalEl {
                 for layout_line in &layout.layout_lines {
                     for layout_cell in &layout_line.cells {
                         let position = vec2f(
-                            origin.x() + layout_cell.point.column as f32 * layout.em_width.0,
+                            (origin.x() + layout_cell.point.column as f32 * layout.em_width.0)
+                                .floor(),
                             origin.y() + layout_cell.point.line as f32 * layout.line_height.0,
                         );
-                        let size = vec2f(layout.em_width.0, layout.line_height.0);
+                        let size = vec2f(layout.em_width.0.ceil(), layout.line_height.0);
 
                         cx.scene.push_quad(Quad {
                             bounds: RectF::new(position, size),
@@ -318,7 +320,7 @@ impl Element for TerminalEl {
 
                         //Don't actually know the start_x for a line, until here:
                         let cell_origin = vec2f(
-                            origin.x() + point.column as f32 * layout.em_width.0,
+                            (origin.x() + point.column as f32 * layout.em_width.0).floor(),
                             origin.y() + point.line as f32 * layout.line_height.0,
                         );
 
@@ -420,14 +422,33 @@ pub fn mouse_to_cell_data(
 
 ///Configures a text style from the current settings.
 fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
+    // Pull the font family from settings properly overriding
+    let family_id = settings
+        .terminal_overrides
+        .font_family
+        .as_ref()
+        .and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
+        .or_else(|| {
+            settings
+                .terminal_defaults
+                .font_family
+                .as_ref()
+                .and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
+        })
+        .unwrap_or(settings.buffer_font_family);
+
     TextStyle {
         color: settings.theme.editor.text_color,
-        font_family_id: settings.buffer_font_family,
-        font_family_name: font_cache.family_name(settings.buffer_font_family).unwrap(),
+        font_family_id: family_id,
+        font_family_name: font_cache.family_name(family_id).unwrap(),
         font_id: font_cache
-            .select_font(settings.buffer_font_family, &Default::default())
+            .select_font(family_id, &Default::default())
             .unwrap(),
-        font_size: settings.buffer_font_size,
+        font_size: settings
+            .terminal_overrides
+            .font_size
+            .or(settings.terminal_defaults.font_size)
+            .unwrap_or(settings.buffer_font_size),
         font_properties: Default::default(),
         underline: Default::default(),
     }

crates/terminal/src/tests/terminal_test_context.rs 🔗

@@ -1,7 +1,7 @@
 use std::time::Duration;
 
 use alacritty_terminal::term::SizeInfo;
-use gpui::{AppContext, ModelContext, ModelHandle, ReadModelWith, TestAppContext};
+use gpui::{AppContext, ModelHandle, ReadModelWith, TestAppContext};
 use itertools::Itertools;
 
 use crate::{
@@ -28,7 +28,8 @@ impl<'a> TerminalTestContext<'a> {
             false,
         );
 
-        let connection = cx.add_model(|cx| TerminalConnection::new(None, size_info, cx));
+        let connection =
+            cx.add_model(|cx| TerminalConnection::new(None, None, None, size_info, cx));
 
         TerminalTestContext { cx, connection }
     }
@@ -56,13 +57,6 @@ impl<'a> TerminalTestContext<'a> {
             })
     }
 
-    pub fn update_connection<F, S>(&mut self, f: F) -> S
-    where
-        F: FnOnce(&mut TerminalConnection, &mut ModelContext<TerminalConnection>) -> S,
-    {
-        self.connection.update(self.cx, |conn, cx| f(conn, cx))
-    }
-
     fn grid_as_str(connection: &TerminalConnection) -> String {
         let term = connection.term.lock();
         let grid_iterator = term.renderable_content().display_iter;

crates/zed/src/settings_file.rs 🔗

@@ -39,15 +39,20 @@ where
         Self(rx)
     }
 
+    ///Loads the given watched JSON file. In the special case that the file is
+    ///empty (ignoring whitespace) or is not a file, this will return T::default()
     async fn load(fs: Arc<dyn Fs>, path: &Path) -> Option<T> {
-        if fs.is_file(&path).await {
-            fs.load(&path)
-                .await
-                .log_err()
-                .and_then(|data| parse_json_with_comments(&data).log_err())
-        } else {
-            Some(T::default())
+        if !fs.is_file(&path).await {
+            return Some(T::default());
         }
+
+        fs.load(&path).await.log_err().and_then(|data| {
+            if data.trim().is_empty() {
+                Some(T::default())
+            } else {
+                parse_json_with_comments(&data).log_err()
+            }
+        })
     }
 }
 

crates/zed/src/zed.rs 🔗

@@ -79,18 +79,25 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
     cx.add_global_action(move |_: &IncreaseBufferFontSize, cx| {
         cx.update_global::<Settings, _, _>(|settings, cx| {
             settings.buffer_font_size = (settings.buffer_font_size + 1.0).max(MIN_FONT_SIZE);
+            if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() {
+                *terminal_font_size = (*terminal_font_size + 1.0).max(MIN_FONT_SIZE);
+            }
             cx.refresh_windows();
         });
     });
     cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| {
         cx.update_global::<Settings, _, _>(|settings, cx| {
             settings.buffer_font_size = (settings.buffer_font_size - 1.0).max(MIN_FONT_SIZE);
+            if let Some(terminal_font_size) = settings.terminal_overrides.font_size.as_mut() {
+                *terminal_font_size = (*terminal_font_size - 1.0).max(MIN_FONT_SIZE);
+            }
             cx.refresh_windows();
         });
     });
     cx.add_global_action(move |_: &ResetBufferFontSize, cx| {
         cx.update_global::<Settings, _, _>(|settings, cx| {
             settings.buffer_font_size = settings.default_buffer_font_size;
+            settings.terminal_overrides.font_size = settings.terminal_defaults.font_size;
             cx.refresh_windows();
         });
     });