Allow overriding editor settings on a per-language basis

Antonio Scandurra created

Change summary

crates/chat_panel/src/lib.rs     |  1 
crates/editor/src/element.rs     |  8 +++-
crates/editor/src/items.rs       | 14 +++++++
crates/editor/src/lib.rs         | 15 ++++++-
crates/file_finder/src/lib.rs    |  1 
crates/go_to_line/src/lib.rs     |  1 
crates/theme_selector/src/lib.rs |  1 
crates/workspace/src/settings.rs | 60 ++++++++++++++++++++++-----------
crates/zed/src/main.rs           | 17 +++++++--
9 files changed, 88 insertions(+), 30 deletions(-)

Detailed changes

crates/chat_panel/src/lib.rs 🔗

@@ -61,6 +61,7 @@ impl ChatPanel {
                         EditorSettings {
                             tab_size: settings.tab_size,
                             style: settings.theme.chat_panel.input_editor.as_editor(),
+                            soft_wrap: editor::SoftWrap::EditorWidth,
                         }
                     }
                 },

crates/editor/src/element.rs 🔗

@@ -1,6 +1,6 @@
 use super::{
     DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll,
-    Select, SelectPhase, Snapshot, MAX_LINE_LEN,
+    Select, SelectPhase, Snapshot, SoftWrap, MAX_LINE_LEN,
 };
 use clock::ReplicaId;
 use gpui::{
@@ -703,7 +703,11 @@ impl Element for EditorElement {
         let em_width = style.text.em_width(cx.font_cache);
         let em_advance = style.text.em_advance(cx.font_cache);
         let overscroll = vec2f(em_width, 0.);
-        let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width;
+        let wrap_width = match self.settings.soft_wrap {
+            SoftWrap::None => None,
+            SoftWrap::EditorWidth => Some(text_width - text_offset.x() - overscroll.x() - em_width),
+            SoftWrap::Column(column) => Some(column as f32 * em_advance),
+        };
         let snapshot = self.update_view(cx.app, |view, cx| {
             if view.set_wrap_width(wrap_width, cx) {
                 view.snapshot(cx)

crates/editor/src/items.rs 🔗

@@ -12,7 +12,8 @@ use project::{ProjectPath, Worktree};
 use std::fmt::Write;
 use std::path::Path;
 use workspace::{
-    EntryOpener, ItemHandle, ItemView, ItemViewHandle, Settings, StatusItemView, WeakItemHandle,
+    settings, EntryOpener, ItemHandle, ItemView, ItemViewHandle, Settings, StatusItemView,
+    WeakItemHandle,
 };
 
 pub struct BufferOpener;
@@ -47,6 +48,7 @@ impl ItemHandle for BufferItemHandle {
         settings: watch::Receiver<Settings>,
         cx: &mut MutableAppContext,
     ) -> Box<dyn ItemViewHandle> {
+        let buffer = self.0.downgrade();
         Box::new(cx.add_view(window_id, |cx| {
             Editor::for_buffer(
                 self.0.clone(),
@@ -71,8 +73,18 @@ impl ItemHandle for BufferItemHandle {
                         font_properties,
                         underline: None,
                     };
+                    let language = buffer.upgrade(cx).and_then(|buf| buf.read(cx).language());
+                    let soft_wrap = match settings.soft_wrap(language) {
+                        settings::SoftWrap::None => crate::SoftWrap::None,
+                        settings::SoftWrap::EditorWidth => crate::SoftWrap::EditorWidth,
+                        settings::SoftWrap::PreferredLineLength => crate::SoftWrap::Column(
+                            settings.preferred_line_length(language).saturating_sub(1),
+                        ),
+                    };
+
                     EditorSettings {
                         tab_size: settings.tab_size,
+                        soft_wrap,
                         style: theme,
                     }
                 },

crates/editor/src/lib.rs 🔗

@@ -333,9 +333,17 @@ pub enum EditorMode {
 #[derive(Clone)]
 pub struct EditorSettings {
     pub tab_size: usize,
+    pub soft_wrap: SoftWrap,
     pub style: EditorStyle,
 }
 
+#[derive(Clone)]
+pub enum SoftWrap {
+    None,
+    EditorWidth,
+    Column(u32),
+}
+
 pub struct Editor {
     handle: WeakViewHandle<Self>,
     buffer: ModelHandle<Buffer>,
@@ -3379,9 +3387,9 @@ impl Editor {
             .text()
     }
 
-    pub fn set_wrap_width(&self, width: f32, cx: &mut MutableAppContext) -> bool {
+    pub fn set_wrap_width(&self, width: Option<f32>, cx: &mut MutableAppContext) -> bool {
         self.display_map
-            .update(cx, |map, cx| map.set_wrap_width(Some(width), cx))
+            .update(cx, |map, cx| map.set_wrap_width(width, cx))
     }
 
     pub fn set_highlighted_row(&mut self, row: Option<u32>) {
@@ -3539,6 +3547,7 @@ impl EditorSettings {
     pub fn test(cx: &AppContext) -> Self {
         Self {
             tab_size: 4,
+            soft_wrap: SoftWrap::None,
             style: {
                 let font_cache: &gpui::FontCache = cx.font_cache();
                 let font_family_name = Arc::from("Monaco");
@@ -4409,7 +4418,7 @@ mod tests {
         let (_, view) = cx.add_window(Default::default(), |cx| build_editor(buffer, settings, cx));
 
         view.update(cx, |view, cx| {
-            view.set_wrap_width(140., cx);
+            view.set_wrap_width(Some(140.), cx);
             assert_eq!(
                 view.display_text(cx),
                 "use one::{\n    two::three::\n    four::five\n};"

crates/file_finder/src/lib.rs 🔗

@@ -275,6 +275,7 @@ impl FileFinder {
                         EditorSettings {
                             style: settings.theme.selector.input_editor.as_editor(),
                             tab_size: settings.tab_size,
+                            soft_wrap: editor::SoftWrap::None,
                         }
                     }
                 },

crates/go_to_line/src/lib.rs 🔗

@@ -54,6 +54,7 @@ impl GoToLine {
                         EditorSettings {
                             tab_size: settings.tab_size,
                             style: settings.theme.selector.input_editor.as_editor(),
+                            soft_wrap: editor::SoftWrap::None,
                         }
                     }
                 },

crates/theme_selector/src/lib.rs 🔗

@@ -69,6 +69,7 @@ impl ThemeSelector {
                         EditorSettings {
                             tab_size: settings.tab_size,
                             style: settings.theme.selector.input_editor.as_editor(),
+                            soft_wrap: editor::SoftWrap::None,
                         }
                     }
                 },

crates/workspace/src/settings.rs 🔗

@@ -1,17 +1,33 @@
 use anyhow::Result;
 use gpui::font_cache::{FamilyId, FontCache};
-use postage::watch;
-use std::sync::Arc;
-use theme::{Theme, ThemeRegistry, DEFAULT_THEME_NAME};
+use language::Language;
+use std::{collections::HashMap, sync::Arc};
+use theme::Theme;
 
 #[derive(Clone)]
 pub struct Settings {
     pub buffer_font_family: FamilyId,
     pub buffer_font_size: f32,
     pub tab_size: usize,
+    pub soft_wrap: SoftWrap,
+    pub preferred_line_length: u32,
+    pub overrides: HashMap<String, Override>,
     pub theme: Arc<Theme>,
 }
 
+#[derive(Clone, Default)]
+pub struct Override {
+    pub soft_wrap: Option<SoftWrap>,
+    pub preferred_line_length: Option<u32>,
+}
+
+#[derive(Copy, Clone)]
+pub enum SoftWrap {
+    None,
+    EditorWidth,
+    PreferredLineLength,
+}
+
 impl Settings {
     pub fn new(
         buffer_font_family: &str,
@@ -22,6 +38,9 @@ impl Settings {
             buffer_font_family: font_cache.load_family(&[buffer_font_family])?,
             buffer_font_size: 16.,
             tab_size: 4,
+            soft_wrap: SoftWrap::None,
+            preferred_line_length: 80,
+            overrides: Default::default(),
             theme,
         })
     }
@@ -30,22 +49,23 @@ impl Settings {
         self.tab_size = tab_size;
         self
     }
-}
 
-pub fn channel(
-    buffer_font_family: &str,
-    font_cache: &FontCache,
-    themes: &ThemeRegistry,
-) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
-    let theme = match themes.get(DEFAULT_THEME_NAME) {
-        Ok(theme) => theme,
-        Err(err) => {
-            panic!("failed to deserialize default theme: {:?}", err)
-        }
-    };
-    Ok(watch::channel_with(Settings::new(
-        buffer_font_family,
-        font_cache,
-        theme,
-    )?))
+    pub fn with_overrides(mut self, language_name: impl Into<String>, overrides: Override) -> Self {
+        self.overrides.insert(language_name.into(), overrides);
+        self
+    }
+
+    pub fn soft_wrap(&self, language: Option<&Arc<Language>>) -> SoftWrap {
+        language
+            .and_then(|language| self.overrides.get(language.name()))
+            .and_then(|settings| settings.soft_wrap)
+            .unwrap_or(self.soft_wrap)
+    }
+
+    pub fn preferred_line_length(&self, language: Option<&Arc<Language>>) -> u32 {
+        language
+            .and_then(|language| self.overrides.get(language.name()))
+            .and_then(|settings| settings.preferred_line_length)
+            .unwrap_or(self.preferred_line_length)
+    }
 }

crates/zed/src/main.rs 🔗

@@ -8,8 +8,8 @@ use log::LevelFilter;
 use parking_lot::Mutex;
 use simplelog::SimpleLogger;
 use std::{fs, path::PathBuf, sync::Arc};
-use theme::ThemeRegistry;
-use workspace::{self, settings, OpenNew};
+use theme::{ThemeRegistry, DEFAULT_THEME_NAME};
+use workspace::{self, settings, OpenNew, Settings};
 use zed::{self, assets::Assets, fs::RealFs, language, menus, AppState, OpenParams, OpenPaths};
 
 fn main() {
@@ -24,8 +24,17 @@ fn main() {
     app.platform().fonts().add_fonts(&embedded_fonts).unwrap();
 
     let themes = ThemeRegistry::new(Assets, app.font_cache());
-    let (settings_tx, settings) =
-        settings::channel("Inconsolata", &app.font_cache(), &themes).unwrap();
+    let theme = themes.get(DEFAULT_THEME_NAME).unwrap();
+    let settings = Settings::new("Inconsolata", &app.font_cache(), theme)
+        .unwrap()
+        .with_overrides(
+            "Markdown",
+            settings::Override {
+                soft_wrap: Some(settings::SoftWrap::PreferredLineLength),
+                ..Default::default()
+            },
+        );
+    let (settings_tx, settings) = postage::watch::channel_with(settings);
     let languages = Arc::new(language::build_language_registry());
     languages.set_theme(&settings.borrow().theme.editor.syntax);