From 47933718389c74757e346fedbaf4754f60f04d55 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 12 Feb 2026 22:52:46 +0200 Subject: [PATCH] Fix semantic highlights not cleared when disabled in settings (#49066) Closes https://github.com/zed-industries/zed/issues/49060 Release Notes: - Fixed semantic highlights not cleared when disabled in settings --- crates/editor/src/editor.rs | 6 +- crates/editor/src/semantic_tokens.rs | 145 +++++++++++++++++++++++---- 2 files changed, 129 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e15fb2050cb60aaaf6dff35d83619525d142e802..4c5cc94b4ff1de96ba9099477d5872de07667fc3 100644 --- a/crates/editor/src/editor.rs +++ b/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); } } diff --git a/crates/editor/src/semantic_tokens.rs b/crates/editor/src/semantic_tokens.rs index e2500b742d0585f43e654aaa1791b3cea4fa50ba..fc309f38568e8e0ba21f99d62c616beb00aa097f 100644 --- a/crates/editor/src/semantic_tokens.rs +++ b/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::>(); + .collect::>(); + + 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::>() + { + 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::( + 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, cx: &TestAppContext,