Tab size is pulled properly from settings instead of hardcoded

Keith Simmons created

Change summary

Cargo.lock                                 |  1 
crates/editor/src/display_map.rs           | 93 ++++++++++++-----------
crates/editor/src/display_map/block_map.rs | 10 +-
crates/editor/src/display_map/tab_map.rs   | 27 +++---
crates/editor/src/display_map/wrap_map.rs  |  5 
crates/editor/src/editor.rs                | 18 ++--
crates/editor/src/movement.rs              | 11 ++
crates/editor/src/test.rs                  |  3 
crates/file_finder/src/file_finder.rs      |  2 
crates/gpui/src/app.rs                     | 15 ++-
crates/project/Cargo.toml                  |  1 
crates/project/src/project.rs              |  7 +
crates/search/src/buffer_search.rs         |  2 
crates/settings/src/settings.rs            |  6 
crates/zed/src/main.rs                     | 14 +++
15 files changed, 128 insertions(+), 87 deletions(-)

Detailed changes

Cargo.lock šŸ”—

@@ -3632,6 +3632,7 @@ dependencies = [
  "rpc",
  "serde",
  "serde_json",
+ "settings",
  "sha2 0.10.2",
  "similar",
  "smol",

crates/editor/src/display_map.rs šŸ”—

@@ -12,6 +12,7 @@ use gpui::{
     Entity, ModelContext, ModelHandle,
 };
 use language::{Point, Subscription as BufferSubscription};
+use settings::Settings;
 use std::{any::TypeId, fmt::Debug, ops::Range, sync::Arc};
 use sum_tree::{Bias, TreeMap};
 use tab_map::TabMap;
@@ -46,8 +47,6 @@ impl Entity for DisplayMap {
 impl DisplayMap {
     pub fn new(
         buffer: ModelHandle<MultiBuffer>,
-        // TODO - remove. read tab_size from settings inside
-        tab_size: usize,
         font_id: FontId,
         font_size: f32,
         wrap_width: Option<f32>,
@@ -56,6 +55,8 @@ impl DisplayMap {
         cx: &mut ModelContext<Self>,
     ) -> Self {
         let buffer_subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
+
+        let tab_size = Self::tab_size(&buffer, cx);
         let (fold_map, snapshot) = FoldMap::new(buffer.read(cx).snapshot(cx));
         let (tab_map, snapshot) = TabMap::new(snapshot, tab_size);
         let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx);
@@ -78,8 +79,8 @@ impl DisplayMap {
         let edits = self.buffer_subscription.consume().into_inner();
         let (folds_snapshot, edits) = self.fold_map.read(buffer_snapshot, edits);
 
-        // TODO: Pull tabsize out of cx and pass it to sync
-        let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits);
+        let tab_size = Self::tab_size(&self.buffer, cx);
+        let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits, tab_size);
         let (wraps_snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx));
@@ -103,14 +104,15 @@ impl DisplayMap {
     ) {
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let edits = self.buffer_subscription.consume().into_inner();
+        let tab_size = Self::tab_size(&self.buffer, cx);
         let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
         self.block_map.read(snapshot, edits);
         let (snapshot, edits) = fold_map.fold(ranges);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -125,14 +127,15 @@ impl DisplayMap {
     ) {
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let edits = self.buffer_subscription.consume().into_inner();
+        let tab_size = Self::tab_size(&self.buffer, cx);
         let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
         self.block_map.read(snapshot, edits);
         let (snapshot, edits) = fold_map.unfold(ranges, inclusive);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -146,8 +149,9 @@ impl DisplayMap {
     ) -> Vec<BlockId> {
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let edits = self.buffer_subscription.consume().into_inner();
+        let tab_size = Self::tab_size(&self.buffer, cx);
         let (snapshot, edits) = self.fold_map.read(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -162,8 +166,9 @@ impl DisplayMap {
     pub fn remove_blocks(&mut self, ids: HashSet<BlockId>, cx: &mut ModelContext<Self>) {
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let edits = self.buffer_subscription.consume().into_inner();
+        let tab_size = Self::tab_size(&self.buffer, cx);
         let (snapshot, edits) = self.fold_map.read(snapshot, edits);
-        let (snapshot, edits) = self.tab_map.sync(snapshot, edits);
+        let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
@@ -198,6 +203,11 @@ impl DisplayMap {
             .update(cx, |map, cx| map.set_wrap_width(width, cx))
     }
 
+    fn tab_size(buffer: &ModelHandle<MultiBuffer>, cx: &mut ModelContext<Self>) -> u32 {
+        let language_name = buffer.read(cx).language(cx).map(|language| language.name());
+        cx.global::<Settings>().tab_size(language_name.as_deref())
+    }
+
     #[cfg(test)]
     pub fn is_rewrapping(&self, cx: &gpui::AppContext) -> bool {
         self.wrap_map.read(cx).is_rewrapping()
@@ -539,6 +549,8 @@ pub mod tests {
         log::info!("tab size: {}", tab_size);
         log::info!("wrap width: {:?}", wrap_width);
 
+        cx.update(|cx| cx.set_global(Settings::test(cx)));
+
         let buffer = cx.update(|cx| {
             if rng.gen() {
                 let len = rng.gen_range(0..10);
@@ -552,7 +564,6 @@ pub mod tests {
         let map = cx.add_model(|cx| {
             DisplayMap::new(
                 buffer.clone(),
-                tab_size,
                 font_id,
                 font_size,
                 wrap_width,
@@ -762,27 +773,18 @@ pub mod tests {
 
         let font_cache = cx.font_cache();
 
-        let tab_size = 4;
         let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
         let font_id = font_cache
             .select_font(family_id, &Default::default())
             .unwrap();
         let font_size = 12.0;
         let wrap_width = Some(64.);
+        cx.set_global(Settings::test(cx));
 
         let text = "one two three four five\nsix seven eight";
         let buffer = MultiBuffer::build_simple(text, cx);
         let map = cx.add_model(|cx| {
-            DisplayMap::new(
-                buffer.clone(),
-                tab_size,
-                font_id,
-                font_size,
-                wrap_width,
-                1,
-                1,
-                cx,
-            )
+            DisplayMap::new(buffer.clone(), font_id, font_size, wrap_width, 1, 1, cx)
         });
 
         let snapshot = map.update(cx, |map, cx| map.snapshot(cx));
@@ -850,18 +852,17 @@ pub mod tests {
 
     #[gpui::test]
     fn test_text_chunks(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         let text = sample_text(6, 6, 'a');
         let buffer = MultiBuffer::build_simple(&text, cx);
-        let tab_size = 4;
         let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
         let font_id = cx
             .font_cache()
             .select_font(family_id, &Default::default())
             .unwrap();
         let font_size = 14.0;
-        let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, 1, cx)
-        });
+        let map =
+            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
         buffer.update(cx, |buffer, cx| {
             buffer.edit(
                 vec![
@@ -926,12 +927,17 @@ pub mod tests {
             .unwrap(),
         );
         language.set_theme(&theme);
+        cx.update(|cx| {
+            cx.set_global(Settings {
+                tab_size: 2,
+                ..Settings::test(cx)
+            })
+        });
 
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
         buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
         let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
 
-        let tab_size = 2;
         let font_cache = cx.font_cache();
         let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
         let font_id = font_cache
@@ -939,8 +945,7 @@ pub mod tests {
             .unwrap();
         let font_size = 14.0;
 
-        let map = cx
-            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
+        let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
         assert_eq!(
             cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
             vec![
@@ -1014,22 +1019,22 @@ pub mod tests {
         );
         language.set_theme(&theme);
 
+        cx.update(|cx| cx.set_global(Settings::test(cx)));
+
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
         buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
         let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
 
         let font_cache = cx.font_cache();
 
-        let tab_size = 4;
         let family_id = font_cache.load_family(&["Courier"]).unwrap();
         let font_id = font_cache
             .select_font(family_id, &Default::default())
             .unwrap();
         let font_size = 16.0;
 
-        let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), 1, 1, cx)
-        });
+        let map =
+            cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, Some(40.0), 1, 1, cx));
         assert_eq!(
             cx.update(|cx| syntax_chunks(0..5, &map, &theme, cx)),
             [
@@ -1061,6 +1066,7 @@ pub mod tests {
     async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
 
+        cx.update(|cx| cx.set_global(Settings::test(cx)));
         let theme = SyntaxTheme::new(vec![
             ("operator".to_string(), Color::red().into()),
             ("string".to_string(), Color::green().into()),
@@ -1093,14 +1099,12 @@ pub mod tests {
         let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
 
         let font_cache = cx.font_cache();
-        let tab_size = 4;
         let family_id = font_cache.load_family(&["Courier"]).unwrap();
         let font_id = font_cache
             .select_font(family_id, &Default::default())
             .unwrap();
         let font_size = 16.0;
-        let map = cx
-            .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
+        let map = cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
 
         enum MyType {}
 
@@ -1139,6 +1143,7 @@ pub mod tests {
 
     #[gpui::test]
     fn test_clip_point(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::MutableAppContext) {
             let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
 
@@ -1190,6 +1195,8 @@ pub mod tests {
 
     #[gpui::test]
     fn test_clip_at_line_ends(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
+
         fn assert(text: &str, cx: &mut gpui::MutableAppContext) {
             let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
             unmarked_snapshot.clip_at_line_ends = true;
@@ -1207,9 +1214,9 @@ pub mod tests {
 
     #[gpui::test]
     fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         let text = "āœ…\t\tα\nβ\t\nšŸ€Ī²\t\tγ";
         let buffer = MultiBuffer::build_simple(text, cx);
-        let tab_size = 4;
         let font_cache = cx.font_cache();
         let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
         let font_id = font_cache
@@ -1217,9 +1224,8 @@ pub mod tests {
             .unwrap();
         let font_size = 14.0;
 
-        let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, 1, cx)
-        });
+        let map =
+            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
         let map = map.update(cx, |map, cx| map.snapshot(cx));
         assert_eq!(map.text(), "āœ…       α\nβ   \nšŸ€Ī²      γ");
         assert_eq!(
@@ -1267,17 +1273,16 @@ pub mod tests {
 
     #[gpui::test]
     fn test_max_point(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
-        let tab_size = 4;
         let font_cache = cx.font_cache();
         let family_id = font_cache.load_family(&["Helvetica"]).unwrap();
         let font_id = font_cache
             .select_font(family_id, &Default::default())
             .unwrap();
         let font_size = 14.0;
-        let map = cx.add_model(|cx| {
-            DisplayMap::new(buffer.clone(), tab_size, font_id, font_size, None, 1, 1, cx)
-        });
+        let map =
+            cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
         assert_eq!(
             map.update(cx, |map, cx| map.snapshot(cx)).max_point(),
             DisplayPoint::new(1, 11)

crates/editor/src/display_map/block_map.rs šŸ”—

@@ -1157,7 +1157,7 @@ mod tests {
 
         let (folds_snapshot, fold_edits) =
             fold_map.read(buffer_snapshot, subscription.consume().into_inner());
-        let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+        let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits, 4);
         let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
             wrap_map.sync(tabs_snapshot, tab_edits, cx)
         });
@@ -1296,7 +1296,8 @@ mod tests {
 
                     let (folds_snapshot, fold_edits) =
                         fold_map.read(buffer_snapshot.clone(), vec![]);
-                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+                    let (tabs_snapshot, tab_edits) =
+                        tab_map.sync(folds_snapshot, fold_edits, tab_size);
                     let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
                         wrap_map.sync(tabs_snapshot, tab_edits, cx)
                     });
@@ -1318,7 +1319,8 @@ mod tests {
 
                     let (folds_snapshot, fold_edits) =
                         fold_map.read(buffer_snapshot.clone(), vec![]);
-                    let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+                    let (tabs_snapshot, tab_edits) =
+                        tab_map.sync(folds_snapshot, fold_edits, tab_size);
                     let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
                         wrap_map.sync(tabs_snapshot, tab_edits, cx)
                     });
@@ -1338,7 +1340,7 @@ mod tests {
             }
 
             let (folds_snapshot, fold_edits) = fold_map.read(buffer_snapshot.clone(), buffer_edits);
-            let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+            let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits, tab_size);
             let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| {
                 wrap_map.sync(tabs_snapshot, tab_edits, cx)
             });

crates/editor/src/display_map/tab_map.rs šŸ”—

@@ -12,7 +12,7 @@ use text::Point;
 pub struct TabMap(Mutex<TabSnapshot>);
 
 impl TabMap {
-    pub fn new(input: FoldSnapshot, tab_size: usize) -> (Self, TabSnapshot) {
+    pub fn new(input: FoldSnapshot, tab_size: u32) -> (Self, TabSnapshot) {
         let snapshot = TabSnapshot {
             fold_snapshot: input,
             tab_size,
@@ -24,12 +24,13 @@ impl TabMap {
         &self,
         fold_snapshot: FoldSnapshot,
         mut fold_edits: Vec<FoldEdit>,
+        tab_size: u32,
     ) -> (TabSnapshot, Vec<TabEdit>) {
         let mut old_snapshot = self.0.lock();
         let max_offset = old_snapshot.fold_snapshot.len();
         let new_snapshot = TabSnapshot {
             fold_snapshot,
-            tab_size: old_snapshot.tab_size,
+            tab_size,
         };
 
         let mut tab_edits = Vec::with_capacity(fold_edits.len());
@@ -87,7 +88,7 @@ impl TabMap {
 #[derive(Clone)]
 pub struct TabSnapshot {
     pub fold_snapshot: FoldSnapshot,
-    pub tab_size: usize,
+    pub tab_size: u32,
 }
 
 impl TabSnapshot {
@@ -234,7 +235,7 @@ impl TabSnapshot {
             .to_buffer_point(&self.fold_snapshot)
     }
 
-    fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
+    fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: u32) -> usize {
         let mut expanded_chars = 0;
         let mut expanded_bytes = 0;
         let mut collapsed_bytes = 0;
@@ -243,7 +244,7 @@ impl TabSnapshot {
                 break;
             }
             if c == '\t' {
-                let tab_len = tab_size - expanded_chars % tab_size;
+                let tab_len = tab_size as usize - expanded_chars % tab_size as usize;
                 expanded_bytes += tab_len;
                 expanded_chars += tab_len;
             } else {
@@ -259,7 +260,7 @@ impl TabSnapshot {
         mut chars: impl Iterator<Item = char>,
         column: usize,
         bias: Bias,
-        tab_size: usize,
+        tab_size: u32,
     ) -> (usize, usize, usize) {
         let mut expanded_bytes = 0;
         let mut expanded_chars = 0;
@@ -270,7 +271,7 @@ impl TabSnapshot {
             }
 
             if c == '\t' {
-                let tab_len = tab_size - (expanded_chars % tab_size);
+                let tab_len = tab_size as usize - (expanded_chars % tab_size as usize);
                 expanded_chars += tab_len;
                 expanded_bytes += tab_len;
                 if expanded_bytes > column {
@@ -383,7 +384,7 @@ pub struct TabChunks<'a> {
     column: usize,
     output_position: Point,
     max_output_position: Point,
-    tab_size: usize,
+    tab_size: u32,
     skip_leading_tab: bool,
 }
 
@@ -415,16 +416,16 @@ impl<'a> Iterator for TabChunks<'a> {
                         });
                     } else {
                         self.chunk.text = &self.chunk.text[1..];
-                        let mut len = self.tab_size - self.column % self.tab_size;
+                        let mut len = self.tab_size - self.column as u32 % self.tab_size;
                         let next_output_position = cmp::min(
-                            self.output_position + Point::new(0, len as u32),
+                            self.output_position + Point::new(0, len),
                             self.max_output_position,
                         );
-                        len = (next_output_position.column - self.output_position.column) as usize;
-                        self.column += len;
+                        len = next_output_position.column - self.output_position.column;
+                        self.column += len as usize;
                         self.output_position = next_output_position;
                         return Some(Chunk {
-                            text: &SPACES[0..len],
+                            text: &SPACES[0..len as usize],
                             ..self.chunk
                         });
                     }

crates/editor/src/display_map/wrap_map.rs šŸ”—

@@ -1104,7 +1104,8 @@ mod tests {
                 }
                 20..=39 => {
                     for (folds_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
-                        let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+                        let (tabs_snapshot, tab_edits) =
+                            tab_map.sync(folds_snapshot, fold_edits, tab_size);
                         let (mut snapshot, wrap_edits) =
                             wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
                         snapshot.check_invariants();
@@ -1129,7 +1130,7 @@ mod tests {
                 "Unwrapped text (unexpanded tabs): {:?}",
                 folds_snapshot.text()
             );
-            let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits);
+            let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits, tab_size);
             log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text());
 
             let unwrapped_text = tabs_snapshot.text();

crates/editor/src/editor.rs šŸ”—

@@ -1010,7 +1010,6 @@ impl Editor {
             let style = build_style(&*settings, get_field_editor_theme, None, cx);
             DisplayMap::new(
                 buffer.clone(),
-                settings.tab_size,
                 style.text.font_id,
                 style.text.font_size,
                 None,
@@ -3006,13 +3005,14 @@ impl Editor {
                                     )
                                     .flat_map(str::chars)
                                     .count();
-                                let chars_to_next_tab_stop = tab_size - (char_column % tab_size);
+                                let chars_to_next_tab_stop =
+                                    tab_size - (char_column as u32 % tab_size);
                                 buffer.edit(
                                     [selection.start..selection.start],
-                                    " ".repeat(chars_to_next_tab_stop),
+                                    " ".repeat(chars_to_next_tab_stop as usize),
                                     cx,
                                 );
-                                selection.start.column += chars_to_next_tab_stop as u32;
+                                selection.start.column += chars_to_next_tab_stop;
                                 selection.end = selection.start;
                             }
                         });
@@ -3055,12 +3055,12 @@ impl Editor {
                     }
 
                     for row in start_row..end_row {
-                        let indent_column = buffer.read(cx).indent_column_for_line(row) as usize;
+                        let indent_column = buffer.read(cx).indent_column_for_line(row);
                         let columns_to_next_tab_stop = tab_size - (indent_column % tab_size);
                         let row_start = Point::new(row, 0);
                         buffer.edit(
                             [row_start..row_start],
-                            " ".repeat(columns_to_next_tab_stop),
+                            " ".repeat(columns_to_next_tab_stop as usize),
                             cx,
                         );
 
@@ -3101,11 +3101,11 @@ impl Editor {
                 }
 
                 for row in rows {
-                    let column = buffer.indent_column_for_line(row) as usize;
+                    let column = buffer.indent_column_for_line(row);
                     if column > 0 {
-                        let mut deletion_len = (column % tab_size) as u32;
+                        let mut deletion_len = column % tab_size;
                         if deletion_len == 0 {
-                            deletion_len = tab_size as u32;
+                            deletion_len = tab_size;
                         }
                         deletion_ranges.push(Point::new(row, 0)..Point::new(row, deletion_len));
                         last_outdent = Some(row);

crates/editor/src/movement.rs šŸ”—

@@ -268,9 +268,11 @@ mod tests {
     use super::*;
     use crate::{test::marked_display_snapshot, Buffer, DisplayMap, MultiBuffer};
     use language::Point;
+    use settings::Settings;
 
     #[gpui::test]
     fn test_previous_word_start(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -297,6 +299,7 @@ mod tests {
 
     #[gpui::test]
     fn test_previous_subword_start(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -330,6 +333,7 @@ mod tests {
 
     #[gpui::test]
     fn test_find_preceding_boundary(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         fn assert(
             marked_text: &str,
             cx: &mut gpui::MutableAppContext,
@@ -361,6 +365,7 @@ mod tests {
 
     #[gpui::test]
     fn test_next_word_end(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -384,6 +389,7 @@ mod tests {
 
     #[gpui::test]
     fn test_next_subword_end(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -416,6 +422,7 @@ mod tests {
 
     #[gpui::test]
     fn test_find_boundary(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         fn assert(
             marked_text: &str,
             cx: &mut gpui::MutableAppContext,
@@ -447,6 +454,7 @@ mod tests {
 
     #[gpui::test]
     fn test_surrounding_word(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         fn assert(marked_text: &str, cx: &mut gpui::MutableAppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -467,6 +475,7 @@ mod tests {
 
     #[gpui::test]
     fn test_move_up_and_down_with_excerpts(cx: &mut gpui::MutableAppContext) {
+        cx.set_global(Settings::test(cx));
         let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
         let font_id = cx
             .font_cache()
@@ -487,7 +496,7 @@ mod tests {
             multibuffer
         });
         let display_map =
-            cx.add_model(|cx| DisplayMap::new(multibuffer, 2, font_id, 14.0, None, 2, 2, cx));
+            cx.add_model(|cx| DisplayMap::new(multibuffer, font_id, 14.0, None, 2, 2, cx));
         let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 
         assert_eq!(snapshot.text(), "\n\nabc\ndefg\n\n\nhijkl\nmn");

crates/editor/src/test.rs šŸ”—

@@ -20,7 +20,6 @@ pub fn marked_display_snapshot(
 ) -> (DisplaySnapshot, Vec<DisplayPoint>) {
     let (unmarked_text, markers) = marked_text(text);
 
-    let tab_size = 4;
     let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap();
     let font_id = cx
         .font_cache()
@@ -30,7 +29,7 @@ pub fn marked_display_snapshot(
 
     let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
     let display_map =
-        cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, 1, 1, cx));
+        cx.add_model(|cx| DisplayMap::new(buffer, font_id, font_size, None, 1, 1, cx));
     let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
     let markers = markers
         .into_iter()

crates/file_finder/src/file_finder.rs šŸ”—

@@ -8,6 +8,7 @@ use gpui::{
     ViewContext, ViewHandle, WeakViewHandle,
 };
 use project::{Project, ProjectPath, WorktreeId};
+use settings::Settings;
 use std::{
     cmp,
     path::Path,
@@ -21,7 +22,6 @@ use workspace::{
     menu::{Confirm, SelectNext, SelectPrev},
     Workspace,
 };
-use settings::Settings;
 
 pub struct FileFinder {
     handle: WeakViewHandle<Self>,

crates/gpui/src/app.rs šŸ”—

@@ -1019,7 +1019,10 @@ impl MutableAppContext {
             .insert(TypeId::of::<A>(), handler)
             .is_some()
         {
-            panic!("registered multiple global handlers for the same action type");
+            panic!(
+                "registered multiple global handlers for {}",
+                type_name::<A>()
+            );
         }
     }
 
@@ -2355,11 +2358,11 @@ impl AppContext {
     }
 
     pub fn global<T: 'static>(&self) -> &T {
-        self.globals
-            .get(&TypeId::of::<T>())
-            .expect("no app state has been added for this type")
-            .downcast_ref()
-            .unwrap()
+        if let Some(global) = self.globals.get(&TypeId::of::<T>()) {
+            global.downcast_ref().unwrap()
+        } else {
+            panic!("no global has been added for {}", type_name::<T>());
+        }
     }
 }
 

crates/project/Cargo.toml šŸ”—

@@ -25,6 +25,7 @@ gpui = { path = "../gpui" }
 language = { path = "../language" }
 lsp = { path = "../lsp" }
 rpc = { path = "../rpc" }
+settings = { path = "../settings" }
 sum_tree = { path = "../sum_tree" }
 util = { path = "../util" }
 aho-corasick = "0.7"

crates/project/src/project.rs šŸ”—

@@ -28,6 +28,7 @@ use parking_lot::Mutex;
 use postage::watch;
 use rand::prelude::*;
 use search::SearchQuery;
+use settings::Settings;
 use sha2::{Digest, Sha256};
 use similar::{ChangeTag, TextDiff};
 use std::{
@@ -2173,6 +2174,10 @@ impl Project {
                     lsp::Url::from_file_path(&buffer_abs_path).unwrap(),
                 );
                 let capabilities = &language_server.capabilities();
+                let tab_size = cx.update(|cx| {
+                    let language_name = buffer.read(cx).language().map(|language| language.name());
+                    cx.global::<Settings>().tab_size(language_name.as_deref())
+                });
                 let lsp_edits = if capabilities
                     .document_formatting_provider
                     .as_ref()
@@ -2182,7 +2187,7 @@ impl Project {
                         .request::<lsp::request::Formatting>(lsp::DocumentFormattingParams {
                             text_document,
                             options: lsp::FormattingOptions {
-                                tab_size: 4,
+                                tab_size,
                                 insert_spaces: true,
                                 insert_final_newline: Some(true),
                                 ..Default::default()

crates/search/src/buffer_search.rs šŸ”—

@@ -8,9 +8,9 @@ use gpui::{
 };
 use language::OffsetRangeExt;
 use project::search::SearchQuery;
+use settings::Settings;
 use std::ops::Range;
 use workspace::{ItemHandle, Pane, ToolbarItemLocation, ToolbarItemView};
-use settings::Settings;
 
 action!(Deploy, bool);
 action!(Dismiss);

crates/settings/src/settings.rs šŸ”—

@@ -11,7 +11,7 @@ pub struct Settings {
     pub buffer_font_family: FamilyId,
     pub buffer_font_size: f32,
     pub vim_mode: bool,
-    pub tab_size: usize,
+    pub tab_size: u32,
     pub soft_wrap: SoftWrap,
     pub preferred_line_length: u32,
     pub language_overrides: HashMap<Arc<str>, LanguageOverride>,
@@ -20,7 +20,7 @@ pub struct Settings {
 
 #[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 pub struct LanguageOverride {
-    pub tab_size: Option<usize>,
+    pub tab_size: Option<u32>,
     pub soft_wrap: Option<SoftWrap>,
     pub preferred_line_length: Option<u32>,
 }
@@ -81,7 +81,7 @@ impl Settings {
         self
     }
 
-    pub fn tab_size(&self, language: Option<&str>) -> usize {
+    pub fn tab_size(&self, language: Option<&str>) -> u32 {
         language
             .and_then(|language| self.language_overrides.get(language))
             .and_then(|settings| settings.tab_size)

crates/zed/src/main.rs šŸ”—

@@ -48,6 +48,20 @@ fn main() {
                 soft_wrap: Some(settings::SoftWrap::PreferredLineLength),
                 ..Default::default()
             },
+        )
+        .with_overrides(
+            "Rust",
+            settings::LanguageOverride {
+                tab_size: Some(4),
+                ..Default::default()
+            },
+        )
+        .with_overrides(
+            "TypeScript",
+            settings::LanguageOverride {
+                tab_size: Some(2),
+                ..Default::default()
+            },
         );
     let settings_file = load_settings_file(&app, fs.clone());