Fix Zed panicking on invalid ranges in semantic token deltas (#50106) (cherry-pick to preview) (#50112)

zed-zippy[bot] and Kirill Bulatov created

Cherry-pick of #50106 to preview

----
Closes ZED-59J

Release Notes:

- Fixed Zed panicking on invalid ranges in semantic token deltas

Co-authored-by: Kirill Bulatov <kirill@zed.dev>

Change summary

crates/project/src/lsp_store/semantic_tokens.rs | 38 ++++++++++++++++++-
1 file changed, 36 insertions(+), 2 deletions(-)

Detailed changes

crates/project/src/lsp_store/semantic_tokens.rs 🔗

@@ -656,8 +656,8 @@ impl ServerSemanticTokens {
 
     pub(crate) fn apply(&mut self, edits: &[SemanticTokensEdit]) {
         for edit in edits {
-            let start = edit.start as usize;
-            let end = start + edit.delete_count as usize;
+            let start = (edit.start as usize).min(self.data.len());
+            let end = (start + edit.delete_count as usize).min(self.data.len());
             self.data.splice(start..end, edit.data.iter().copied());
         }
     }
@@ -1003,4 +1003,38 @@ mod tests {
             ]
         );
     }
+
+    #[test]
+    fn applies_out_of_bounds_delta_edit_without_panic() {
+        let mut tokens = ServerSemanticTokens::from_full(vec![2, 5, 3, 0, 3, 0, 5, 4, 1, 0], None);
+
+        // start beyond data length
+        tokens.apply(&[SemanticTokensEdit {
+            start: 100,
+            delete_count: 5,
+            data: vec![1, 2, 3, 4, 5],
+        }]);
+        assert_eq!(
+            tokens.data,
+            vec![2, 5, 3, 0, 3, 0, 5, 4, 1, 0, 1, 2, 3, 4, 5]
+        );
+
+        // delete_count extends past data length
+        let mut tokens = ServerSemanticTokens::from_full(vec![2, 5, 3, 0, 3], None);
+        tokens.apply(&[SemanticTokensEdit {
+            start: 3,
+            delete_count: 100,
+            data: vec![9, 9],
+        }]);
+        assert_eq!(tokens.data, vec![2, 5, 3, 9, 9]);
+
+        // empty data
+        let mut tokens = ServerSemanticTokens::from_full(Vec::new(), None);
+        tokens.apply(&[SemanticTokensEdit {
+            start: 0,
+            delete_count: 5,
+            data: vec![1, 2, 3, 4, 5],
+        }]);
+        assert_eq!(tokens.data, vec![1, 2, 3, 4, 5]);
+    }
 }