From fbdf5d4df49bf3633a012b01143e1bbc4b2e6e39 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 27 Sep 2025 00:13:16 +0200 Subject: [PATCH] editor: Do not panic on `tab_size > 16`, cap it at 128 (#38994) Fixes ZED-1PT Fixes ZED-1PW Fixes ZED-1G2 Release Notes: - Fixed Zed panicking when the `tab_size` is set higher than 16 --- crates/editor/src/display_map/block_map.rs | 6 ++--- crates/editor/src/display_map/tab_map.rs | 23 +++++++++---------- crates/language/src/language.rs | 2 ++ .../settings/src/settings_content/language.rs | 1 + 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 614b322703d732300e7679d9af3b6c737cb8e9b8..c5c2bac24b2f823edd762b33750aa394230ab5f7 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/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; } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 6b0227ff3ccf1390ec2812687ce96255c55601ce..50e7363de7a09bfef6d380040f746891c7c9cbfd 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/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, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index b006b2785f7be2674b23849b2418ef2c77117fa0..1d172576ec454fd20449f28b6fbddeb67be783a2 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -749,6 +749,7 @@ pub struct LanguageConfig { pub hard_tabs: Option, /// How many columns a tab should occupy. #[serde(default)] + #[schemars(range(min = 1, max = 128))] pub tab_size: Option, /// 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, /// A indent to add for prefix and end line upon new line. + #[schemars(range(min = 1, max = 128))] pub tab_size: u32, } diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs index 0ff334e4aca5907f5204602e42a4a462d2b00450..fe04e9fff638252d4586a32fd6031ca0d22b6771 100644 --- a/crates/settings/src/settings_content/language.rs +++ b/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, /// Whether to indent lines using tab characters, as opposed to multiple /// spaces.