editor: Do not panic on `tab_size > 16`, cap it at 128 (#38994)

Lukas Wirth created

Fixes ZED-1PT
Fixes ZED-1PW
Fixes ZED-1G2

Release Notes:

- Fixed Zed panicking when the `tab_size` is set higher than 16

Change summary

crates/editor/src/display_map/block_map.rs       |  6 ++--
crates/editor/src/display_map/tab_map.rs         | 23 ++++++++---------
crates/language/src/language.rs                  |  2 +
crates/settings/src/settings_content/language.rs |  1 
4 files changed, 17 insertions(+), 15 deletions(-)

Detailed changes

crates/editor/src/display_map/block_map.rs 🔗

@@ -26,8 +26,8 @@ use sum_tree::{Bias, ContextLessSummary, Dimensions, SumTree, TreeMap};
 use text::{BufferId, Edit};
 use ui::ElementId;
 
-const NEWLINES: &[u8] = &[b'\n'; u128::BITS as usize];
-const BULLETS: &str = "********************************************************************************************************************************";
+const NEWLINES: &[u8; u128::BITS as usize] = &[b'\n'; _];
+const BULLETS: &[u8; u128::BITS as usize] = &[b'*'; _];
 
 /// Tracks custom blocks such as diagnostics that should be displayed within buffer.
 ///
@@ -1774,7 +1774,7 @@ impl<'a> Iterator for BlockChunks<'a> {
             // need to have the same number of bytes in the input as output.
             let chars_count = prefix.chars().count();
             let bullet_len = chars_count;
-            prefix = &BULLETS[..bullet_len];
+            prefix = unsafe { std::str::from_utf8_unchecked(&BULLETS[..bullet_len]) };
             chars = 1u128.unbounded_shl(bullet_len as u32) - 1;
             tabs = 0;
         }

crates/editor/src/display_map/tab_map.rs 🔗

@@ -10,6 +10,10 @@ use sum_tree::Bias;
 
 const MAX_EXPANSION_COLUMN: u32 = 256;
 
+// Handles a tab width <= 128
+const SPACES: &[u8; u128::BITS as usize] = &[b' '; _];
+const MAX_TABS: NonZeroU32 = NonZeroU32::new(SPACES.len() as u32).unwrap();
+
 /// Keeps track of hard tabs in a text buffer.
 ///
 /// See the [`display_map` module documentation](crate::display_map) for more information.
@@ -19,7 +23,7 @@ impl TabMap {
     pub fn new(fold_snapshot: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {
         let snapshot = TabSnapshot {
             fold_snapshot,
-            tab_size,
+            tab_size: tab_size.min(MAX_TABS),
             max_expansion_column: MAX_EXPANSION_COLUMN,
             version: 0,
         };
@@ -41,7 +45,7 @@ impl TabMap {
         let old_snapshot = &mut self.0;
         let mut new_snapshot = TabSnapshot {
             fold_snapshot,
-            tab_size,
+            tab_size: tab_size.min(MAX_TABS),
             max_expansion_column: old_snapshot.max_expansion_column,
             version: old_snapshot.version,
         };
@@ -266,7 +270,7 @@ impl TabSnapshot {
             max_output_position: range.end.0,
             tab_size: self.tab_size,
             chunk: Chunk {
-                text: &SPACES[0..(to_next_stop as usize)],
+                text: unsafe { std::str::from_utf8_unchecked(&SPACES[..to_next_stop as usize]) },
                 is_tab: true,
                 ..Default::default()
             },
@@ -317,13 +321,11 @@ impl TabSnapshot {
         let (collapsed, expanded_char_column, to_next_stop) =
             self.collapse_tabs(tab_cursor, expanded, bias);
 
-        let result = (
+        (
             FoldPoint::new(output.row(), collapsed),
             expanded_char_column,
             to_next_stop,
-        );
-
-        result
+        )
     }
 
     pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
@@ -510,9 +512,6 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
     }
 }
 
-// Handles a tab width <= 16
-const SPACES: &str = "                ";
-
 pub struct TabChunks<'a> {
     snapshot: &'a TabSnapshot,
     fold_chunks: FoldChunks<'a>,
@@ -549,7 +548,7 @@ impl TabChunks<'_> {
         self.output_position = range.start.0;
         self.max_output_position = range.end.0;
         self.chunk = Chunk {
-            text: &SPACES[0..(to_next_stop as usize)],
+            text: unsafe { std::str::from_utf8_unchecked(&SPACES[..to_next_stop as usize]) },
             is_tab: true,
             chars: 1u128.unbounded_shl(to_next_stop) - 1,
             ..Default::default()
@@ -621,7 +620,7 @@ impl<'a> Iterator for TabChunks<'a> {
                         self.input_column += 1;
                         self.output_position = next_output_position;
                         return Some(Chunk {
-                            text: &SPACES[..len as usize],
+                            text: unsafe { std::str::from_utf8_unchecked(&SPACES[..len as usize]) },
                             is_tab: true,
                             chars: 1u128.unbounded_shl(len) - 1,
                             tabs: 0,

crates/language/src/language.rs 🔗

@@ -749,6 +749,7 @@ pub struct LanguageConfig {
     pub hard_tabs: Option<bool>,
     /// How many columns a tab should occupy.
     #[serde(default)]
+    #[schemars(range(min = 1, max = 128))]
     pub tab_size: Option<NonZeroU32>,
     /// How to soft-wrap long lines of text.
     #[serde(default)]
@@ -845,6 +846,7 @@ pub struct BlockCommentConfig {
     /// A character to add as a prefix when a new line is added to a block comment.
     pub prefix: Arc<str>,
     /// A indent to add for prefix and end line upon new line.
+    #[schemars(range(min = 1, max = 128))]
     pub tab_size: u32,
 }
 

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

@@ -170,6 +170,7 @@ pub struct LanguageSettingsContent {
     /// How many columns a tab should occupy.
     ///
     /// Default: 4
+    #[schemars(range(min = 1, max = 128))]
     pub tab_size: Option<NonZeroU32>,
     /// Whether to indent lines using tab characters, as opposed to multiple
     /// spaces.