Fix semantic highlights not cleared when disabled in settings (#49066)

Kirill Bulatov created

Closes https://github.com/zed-industries/zed/issues/49060

Release Notes:

- Fixed semantic highlights not cleared when disabled in settings

Change summary

crates/editor/src/editor.rs          |   6 
crates/editor/src/semantic_tokens.rs | 145 ++++++++++++++++++++++++++---
2 files changed, 129 insertions(+), 22 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -24217,10 +24217,10 @@ impl Editor {
                 .global_lsp_settings
                 .semantic_token_rules
                 .clone();
-            if self
+            let semantic_token_rules_changed = self
                 .semantic_token_state
-                .update_rules(new_semantic_token_rules)
-            {
+                .update_rules(new_semantic_token_rules);
+            if language_settings_changed || semantic_token_rules_changed {
                 self.refresh_semantic_token_highlights(cx);
             }
         }

crates/editor/src/semantic_tokens.rs 🔗

@@ -5,7 +5,6 @@ use futures::future::join_all;
 use gpui::{
     App, Context, FontStyle, FontWeight, HighlightStyle, StrikethroughStyle, Task, UnderlineStyle,
 };
-use itertools::Itertools as _;
 use language::language_settings::language_settings;
 use project::{
     lsp_store::{
@@ -157,8 +156,35 @@ impl Editor {
                     None
                 }
             })
-            .unique_by(|(buffer_id, _)| *buffer_id)
-            .collect::<Vec<_>>();
+            .collect::<HashMap<_, _>>();
+
+        for buffer_with_disabled_tokens in self
+            .display_map
+            .read(cx)
+            .semantic_token_highlights
+            .iter()
+            .map(|(buffer_id, _)| *buffer_id)
+            .filter(|buffer_id| !buffers_to_query.contains_key(buffer_id))
+            .filter(|buffer_id| {
+                !self
+                    .buffer
+                    .read(cx)
+                    .buffer(*buffer_id)
+                    .is_some_and(|buffer| {
+                        let buffer = buffer.read(cx);
+                        language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
+                            .semantic_tokens
+                            .enabled()
+                    })
+            })
+            .collect::<Vec<_>>()
+        {
+            self.semantic_token_state
+                .invalidate_buffer(&buffer_with_disabled_tokens);
+            self.display_map.update(cx, |display_map, _| {
+                display_map.invalidate_semantic_highlights(buffer_with_disabled_tokens);
+            });
+        }
 
         self.semantic_token_state.update_task = cx.spawn(async move |editor, cx| {
             cx.background_executor()
@@ -389,7 +415,7 @@ fn convert_token(
                     SemanticTokenColorOverride::InheritForeground(false) => None,
                     SemanticTokenColorOverride::Replace(c) => Some(c.into()),
                 },
-                ..Default::default()
+                ..UnderlineStyle::default()
             }
         });
 
@@ -449,7 +475,7 @@ mod tests {
                 "Rust".into(),
                 LanguageSettingsContent {
                     semantic_tokens: Some(SemanticTokens::Full),
-                    ..Default::default()
+                    ..LanguageSettingsContent::default()
                 },
             );
         });
@@ -527,7 +553,7 @@ mod tests {
                 "Rust".into(),
                 LanguageSettingsContent {
                     semantic_tokens: Some(SemanticTokens::Full),
-                    ..Default::default()
+                    ..LanguageSettingsContent::default()
                 },
             );
         });
@@ -603,7 +629,7 @@ mod tests {
                 "Rust".into(),
                 LanguageSettingsContent {
                     semantic_tokens: Some(SemanticTokens::Full),
-                    ..Default::default()
+                    ..LanguageSettingsContent::default()
                 },
             );
         });
@@ -699,7 +725,7 @@ mod tests {
                 "TOML".into(),
                 LanguageSettingsContent {
                     semantic_tokens: Some(SemanticTokens::Full),
-                    ..Default::default()
+                    ..LanguageSettingsContent::default()
                 },
             );
         });
@@ -709,9 +735,9 @@ mod tests {
                 name: "TOML".into(),
                 matcher: LanguageMatcher {
                     path_suffixes: vec!["toml".into()],
-                    ..Default::default()
+                    ..LanguageMatcher::default()
                 },
-                ..Default::default()
+                ..LanguageConfig::default()
             },
             None,
         ));
@@ -920,14 +946,14 @@ mod tests {
                 "TOML".into(),
                 LanguageSettingsContent {
                     semantic_tokens: Some(SemanticTokens::Full),
-                    ..Default::default()
+                    ..LanguageSettingsContent::default()
                 },
             );
             language_settings.languages.0.insert(
                 "Rust".into(),
                 LanguageSettingsContent {
                     semantic_tokens: Some(SemanticTokens::Full),
-                    ..Default::default()
+                    ..LanguageSettingsContent::default()
                 },
             );
         });
@@ -937,9 +963,9 @@ mod tests {
                 name: "TOML".into(),
                 matcher: LanguageMatcher {
                     path_suffixes: vec!["toml".into()],
-                    ..Default::default()
+                    ..LanguageMatcher::default()
                 },
-                ..Default::default()
+                ..LanguageConfig::default()
             },
             None,
         ));
@@ -948,9 +974,9 @@ mod tests {
                 name: "Rust".into(),
                 matcher: LanguageMatcher {
                     path_suffixes: vec!["rs".into()],
-                    ..Default::default()
+                    ..LanguageMatcher::default()
                 },
-                ..Default::default()
+                ..LanguageConfig::default()
             },
             None,
         ));
@@ -1205,7 +1231,7 @@ mod tests {
                 "TOML".into(),
                 LanguageSettingsContent {
                     semantic_tokens: Some(SemanticTokens::Full),
-                    ..Default::default()
+                    ..LanguageSettingsContent::default()
                 },
             );
         });
@@ -1215,9 +1241,9 @@ mod tests {
                 name: "TOML".into(),
                 matcher: LanguageMatcher {
                     path_suffixes: vec!["toml".into()],
-                    ..Default::default()
+                    ..LanguageMatcher::default()
                 },
-                ..Default::default()
+                ..LanguageConfig::default()
             },
             None,
         ));
@@ -1887,6 +1913,87 @@ mod tests {
         );
     }
 
+    #[gpui::test]
+    async fn test_disabling_semantic_tokens_setting_clears_highlights(cx: &mut TestAppContext) {
+        init_test(cx, |_| {});
+
+        update_test_language_settings(cx, |language_settings| {
+            language_settings.languages.0.insert(
+                "Rust".into(),
+                LanguageSettingsContent {
+                    semantic_tokens: Some(SemanticTokens::Full),
+                    ..LanguageSettingsContent::default()
+                },
+            );
+        });
+
+        let mut cx = EditorLspTestContext::new_rust(
+            lsp::ServerCapabilities {
+                semantic_tokens_provider: Some(
+                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
+                        lsp::SemanticTokensOptions {
+                            legend: lsp::SemanticTokensLegend {
+                                token_types: vec!["function".into()],
+                                token_modifiers: Vec::new(),
+                            },
+                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
+                            ..lsp::SemanticTokensOptions::default()
+                        },
+                    ),
+                ),
+                ..lsp::ServerCapabilities::default()
+            },
+            cx,
+        )
+        .await;
+
+        let mut full_request = cx
+            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
+                move |_, _, _| async move {
+                    Ok(Some(lsp::SemanticTokensResult::Tokens(
+                        lsp::SemanticTokens {
+                            data: vec![
+                                0, // delta_line
+                                3, // delta_start
+                                4, // length
+                                0, // token_type
+                                0, // token_modifiers_bitset
+                            ],
+                            result_id: None,
+                        },
+                    )))
+                },
+            );
+
+        cx.set_state("ˇfn main() {}");
+        assert!(full_request.next().await.is_some());
+        cx.run_until_parked();
+
+        assert_eq!(
+            extract_semantic_highlights(&cx.editor, &cx),
+            vec![MultiBufferOffset(3)..MultiBufferOffset(7)],
+            "Semantic tokens should be present before disabling the setting"
+        );
+
+        update_test_language_settings(&mut cx, |language_settings| {
+            language_settings.languages.0.insert(
+                "Rust".into(),
+                LanguageSettingsContent {
+                    semantic_tokens: Some(SemanticTokens::Off),
+                    ..LanguageSettingsContent::default()
+                },
+            );
+        });
+        cx.executor().advance_clock(Duration::from_millis(200));
+        cx.run_until_parked();
+
+        assert_eq!(
+            extract_semantic_highlights(&cx.editor, &cx),
+            Vec::new(),
+            "Semantic tokens should be cleared after disabling the setting"
+        );
+    }
+
     fn extract_semantic_highlight_styles(
         editor: &Entity<Editor>,
         cx: &TestAppContext,