Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:34:25.381Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

semantic_tokens.rs

2588 lines · 101.6 KB · rust
1use std::{collections::hash_map, sync::Arc, time::Duration};
2
3use collections::{HashMap, HashSet};
4use futures::future::join_all;
5use gpui::{
6    App, Context, FontStyle, FontWeight, HighlightStyle, StrikethroughStyle, Task, UnderlineStyle,
7};
8use itertools::Itertools;
9use language::language_settings::LanguageSettings;
10use project::{
11    lsp_store::{BufferSemanticToken, BufferSemanticTokens, SemanticTokenStylizer, TokenType},
12    project_settings::ProjectSettings,
13};
14use settings::{
15    SemanticTokenColorOverride, SemanticTokenFontStyle, SemanticTokenFontWeight, SemanticTokenRule,
16    SemanticTokenRules, Settings as _,
17};
18use text::BufferId;
19use theme::SyntaxTheme;
20use ui::ActiveTheme as _;
21
22use crate::{
23    Editor,
24    actions::ToggleSemanticHighlights,
25    display_map::{HighlightStyleInterner, SemanticTokenHighlight},
26};
27
28pub(super) struct SemanticTokenState {
29    rules: SemanticTokenRules,
30    enabled: bool,
31    update_task: Task<()>,
32    fetched_for_buffers: HashMap<BufferId, clock::Global>,
33}
34
35impl SemanticTokenState {
36    pub(super) fn new(cx: &App, enabled: bool) -> Self {
37        Self {
38            rules: ProjectSettings::get_global(cx)
39                .global_lsp_settings
40                .semantic_token_rules
41                .clone(),
42            enabled,
43            update_task: Task::ready(()),
44            fetched_for_buffers: HashMap::default(),
45        }
46    }
47
48    pub(super) fn enabled(&self) -> bool {
49        self.enabled
50    }
51
52    pub(super) fn toggle_enabled(&mut self) {
53        self.enabled = !self.enabled;
54    }
55
56    #[cfg(test)]
57    pub(super) fn take_update_task(&mut self) -> Task<()> {
58        std::mem::replace(&mut self.update_task, Task::ready(()))
59    }
60
61    pub(super) fn invalidate_buffer(&mut self, buffer_id: &BufferId) {
62        self.fetched_for_buffers.remove(buffer_id);
63    }
64
65    pub(super) fn update_rules(&mut self, new_rules: SemanticTokenRules) -> bool {
66        if new_rules != self.rules {
67            self.rules = new_rules;
68            true
69        } else {
70            false
71        }
72    }
73}
74
75impl Editor {
76    pub fn supports_semantic_tokens(&self, cx: &mut App) -> bool {
77        let Some(provider) = self.semantics_provider.as_ref() else {
78            return false;
79        };
80
81        let mut supports = false;
82        self.buffer().update(cx, |this, cx| {
83            this.for_each_buffer(&mut |buffer| {
84                supports |= provider.supports_semantic_tokens(buffer, cx);
85            });
86        });
87
88        supports
89    }
90
91    pub fn semantic_highlights_enabled(&self) -> bool {
92        self.semantic_token_state.enabled()
93    }
94
95    pub fn toggle_semantic_highlights(
96        &mut self,
97        _: &ToggleSemanticHighlights,
98        _window: &mut gpui::Window,
99        cx: &mut Context<Self>,
100    ) {
101        self.semantic_token_state.toggle_enabled();
102        self.invalidate_semantic_tokens(None);
103        self.refresh_semantic_tokens(None, false, cx);
104    }
105
106    pub(super) fn invalidate_semantic_tokens(&mut self, for_buffer: Option<BufferId>) {
107        match for_buffer {
108            Some(for_buffer) => self.semantic_token_state.invalidate_buffer(&for_buffer),
109            None => self.semantic_token_state.fetched_for_buffers.clear(),
110        }
111    }
112
113    pub(super) fn refresh_semantic_tokens(
114        &mut self,
115        buffer_id: Option<BufferId>,
116        server_refreshed: bool,
117        cx: &mut Context<Self>,
118    ) {
119        if !self.lsp_data_enabled() || !self.semantic_token_state.enabled() {
120            self.invalidate_semantic_tokens(None);
121            self.display_map.update(cx, |display_map, _| {
122                match Arc::get_mut(&mut display_map.semantic_token_highlights) {
123                    Some(highlights) => highlights.clear(),
124                    None => display_map.semantic_token_highlights = Arc::new(Default::default()),
125                };
126            });
127            self.semantic_token_state.update_task = Task::ready(());
128            cx.notify();
129            return;
130        }
131
132        let mut invalidate_semantic_highlights_for_buffers = HashSet::default();
133        if server_refreshed {
134            invalidate_semantic_highlights_for_buffers.extend(
135                self.semantic_token_state
136                    .fetched_for_buffers
137                    .drain()
138                    .map(|(buffer_id, _)| buffer_id),
139            );
140        }
141
142        let Some((sema, project)) = self
143            .semantics_provider
144            .clone()
145            .zip(self.project.as_ref().map(|p| p.downgrade()))
146        else {
147            return;
148        };
149
150        let buffers_to_query = self
151            .visible_buffers(cx)
152            .into_iter()
153            .filter(|buffer| self.is_lsp_relevant(buffer.read(cx).file(), cx))
154            .chain(buffer_id.and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id)))
155            .filter_map(|editor_buffer| {
156                let editor_buffer_id = editor_buffer.read(cx).remote_id();
157                if self.registered_buffers.contains_key(&editor_buffer_id)
158                    && LanguageSettings::for_buffer(editor_buffer.read(cx), cx)
159                        .semantic_tokens
160                        .enabled()
161                {
162                    Some((editor_buffer_id, editor_buffer))
163                } else {
164                    None
165                }
166            })
167            .collect::<HashMap<_, _>>();
168
169        for buffer_with_disabled_tokens in self
170            .display_map
171            .read(cx)
172            .semantic_token_highlights
173            .keys()
174            .copied()
175            .filter(|buffer_id| !buffers_to_query.contains_key(buffer_id))
176            .filter(|buffer_id| {
177                !self
178                    .buffer
179                    .read(cx)
180                    .buffer(*buffer_id)
181                    .is_some_and(|buffer| {
182                        let buffer = buffer.read(cx);
183                        LanguageSettings::for_buffer(&buffer, cx)
184                            .semantic_tokens
185                            .enabled()
186                    })
187            })
188            .collect::<Vec<_>>()
189        {
190            self.semantic_token_state
191                .invalidate_buffer(&buffer_with_disabled_tokens);
192            self.display_map.update(cx, |display_map, _| {
193                display_map.invalidate_semantic_highlights(buffer_with_disabled_tokens);
194            });
195        }
196
197        self.semantic_token_state.update_task = cx.spawn(async move |editor, cx| {
198            cx.background_executor()
199                .timer(Duration::from_millis(50))
200                .await;
201            let Some(all_semantic_tokens_task) = editor
202                .update(cx, |editor, cx| {
203                    buffers_to_query
204                        .into_iter()
205                        .filter_map(|(buffer_id, buffer)| {
206                            let known_version = editor
207                                .semantic_token_state
208                                .fetched_for_buffers
209                                .get(&buffer_id);
210                            let query_version = buffer.read(cx).version();
211                            if known_version.is_some_and(|known_version| {
212                                !query_version.changed_since(known_version)
213                            }) {
214                                None
215                            } else {
216                                sema.semantic_tokens(buffer, cx).map(|task| async move {
217                                    (buffer_id, query_version, task.await)
218                                })
219                            }
220                        })
221                        .collect::<Vec<_>>()
222                })
223                .ok()
224            else {
225                return;
226            };
227
228            let all_semantic_tokens = join_all(all_semantic_tokens_task).await;
229            editor
230                .update(cx, |editor, cx| {
231                    editor.display_map.update(cx, |display_map, _| {
232                        for buffer_id in invalidate_semantic_highlights_for_buffers {
233                            display_map.invalidate_semantic_highlights(buffer_id);
234                            editor.semantic_token_state.invalidate_buffer(&buffer_id);
235                        }
236                    });
237
238                    if all_semantic_tokens.is_empty() {
239                        return;
240                    }
241                    let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
242
243                    for (buffer_id, query_version, tokens) in all_semantic_tokens {
244                        let tokens = match tokens {
245                            Ok(BufferSemanticTokens {
246                                tokens: Some(tokens),
247                            }) => tokens,
248                            Ok(BufferSemanticTokens { tokens: None }) => {
249                                editor.display_map.update(cx, |display_map, _| {
250                                    display_map.invalidate_semantic_highlights(buffer_id);
251                                });
252                                continue;
253                            }
254                            Err(e) => {
255                                log::error!(
256                                    "Failed to fetch semantic tokens for buffer \
257                                    {buffer_id:?}: {e:#}"
258                                );
259                                continue;
260                            }
261                        };
262
263                        match editor
264                            .semantic_token_state
265                            .fetched_for_buffers
266                            .entry(buffer_id)
267                        {
268                            hash_map::Entry::Occupied(mut o) => {
269                                if query_version.changed_since(o.get()) {
270                                    o.insert(query_version);
271                                } else {
272                                    continue;
273                                }
274                            }
275                            hash_map::Entry::Vacant(v) => {
276                                v.insert(query_version);
277                            }
278                        }
279
280                        let language_name = editor
281                            .buffer()
282                            .read(cx)
283                            .buffer(buffer_id)
284                            .and_then(|buf| buf.read(cx).language().map(|l| l.name()));
285
286                        let Some(project) = project.upgrade() else {
287                            return;
288                        };
289                        editor.display_map.update(cx, |display_map, cx| {
290                            project.read(cx).lsp_store().update(cx, |lsp_store, cx| {
291                                let mut token_highlights = Vec::new();
292                                let mut interner = HighlightStyleInterner::default();
293                                for (server_id, server_tokens) in tokens {
294                                    let Some(stylizer) = lsp_store.get_or_create_token_stylizer(
295                                        server_id,
296                                        language_name.as_ref(),
297                                        cx,
298                                    ) else {
299                                        continue;
300                                    };
301                                    let theme = cx.theme().syntax();
302                                    token_highlights.reserve(2 * server_tokens.len());
303                                    token_highlights.extend(buffer_into_editor_highlights(
304                                        &server_tokens,
305                                        stylizer,
306                                        &multi_buffer_snapshot,
307                                        &mut interner,
308                                        theme,
309                                    ));
310                                }
311
312                                token_highlights.sort_by(|a, b| {
313                                    a.range.start.cmp(&b.range.start, &multi_buffer_snapshot)
314                                });
315                                Arc::make_mut(&mut display_map.semantic_token_highlights).insert(
316                                    buffer_id,
317                                    (Arc::from(token_highlights), Arc::new(interner)),
318                                );
319                            });
320                        });
321                    }
322
323                    cx.notify();
324                })
325                .ok();
326        });
327    }
328}
329
330fn buffer_into_editor_highlights<'a, 'b>(
331    buffer_tokens: &'a [BufferSemanticToken],
332    stylizer: &'a SemanticTokenStylizer,
333    multi_buffer_snapshot: &'a multi_buffer::MultiBufferSnapshot,
334    interner: &'b mut HighlightStyleInterner,
335    theme: &'a SyntaxTheme,
336) -> impl Iterator<Item = SemanticTokenHighlight> + use<'a, 'b> {
337    multi_buffer_snapshot
338        .text_anchors_to_visible_anchors(
339            buffer_tokens
340                .iter()
341                .flat_map(|token| [token.range.start, token.range.end]),
342        )
343        .into_iter()
344        .tuples::<(_, _)>()
345        .zip(buffer_tokens)
346        .filter_map(|((multi_buffer_start, multi_buffer_end), token)| {
347            let range = multi_buffer_start?..multi_buffer_end?;
348            let style = convert_token(stylizer, theme, token.token_type, token.token_modifiers)?;
349            let style = interner.intern(style);
350            Some(SemanticTokenHighlight {
351                range,
352                style,
353                token_type: token.token_type,
354                token_modifiers: token.token_modifiers,
355                server_id: stylizer.server_id(),
356            })
357        })
358}
359
360fn convert_token(
361    stylizer: &SemanticTokenStylizer,
362    theme: &SyntaxTheme,
363    token_type: TokenType,
364    modifiers: u32,
365) -> Option<HighlightStyle> {
366    let rules = stylizer.rules_for_token(token_type)?;
367    let filter = |rule: &&SemanticTokenRule| {
368        rule.token_modifiers
369            .iter()
370            .all(|m| stylizer.has_modifier(modifiers, m))
371    };
372    let last = rules.last()?;
373    if last.no_style_defined() && filter(&last) {
374        return None;
375    }
376
377    let mut highlight = HighlightStyle::default();
378
379    for rule in rules.into_iter().filter(filter) {
380        let style = rule
381            .style
382            .iter()
383            .find_map(|style| theme.style_for_name(style));
384
385        macro_rules! overwrite {
386            (
387                highlight.$highlight_field:ident,
388                SemanticTokenRule::$rule_field:ident,
389                $transform:expr $(,)?
390            ) => {
391                highlight.$highlight_field = rule
392                    .$rule_field
393                    .map($transform)
394                    .or_else(|| style.as_ref().and_then(|s| s.$highlight_field))
395                    .or(highlight.$highlight_field)
396            };
397        }
398
399        overwrite!(
400            highlight.color,
401            SemanticTokenRule::foreground_color,
402            Into::into,
403        );
404
405        overwrite!(
406            highlight.background_color,
407            SemanticTokenRule::background_color,
408            Into::into,
409        );
410
411        overwrite!(
412            highlight.font_weight,
413            SemanticTokenRule::font_weight,
414            |w| match w {
415                SemanticTokenFontWeight::Normal => FontWeight::NORMAL,
416                SemanticTokenFontWeight::Bold => FontWeight::BOLD,
417            },
418        );
419
420        overwrite!(
421            highlight.font_style,
422            SemanticTokenRule::font_style,
423            |s| match s {
424                SemanticTokenFontStyle::Normal => FontStyle::Normal,
425                SemanticTokenFontStyle::Italic => FontStyle::Italic,
426            },
427        );
428
429        overwrite!(highlight.underline, SemanticTokenRule::underline, |u| {
430            UnderlineStyle {
431                thickness: 1.0.into(),
432                color: match u {
433                    SemanticTokenColorOverride::InheritForeground(true) => highlight.color,
434                    SemanticTokenColorOverride::InheritForeground(false) => None,
435                    SemanticTokenColorOverride::Replace(c) => Some(c.into()),
436                },
437                ..UnderlineStyle::default()
438            }
439        });
440
441        overwrite!(
442            highlight.strikethrough,
443            SemanticTokenRule::strikethrough,
444            |s| StrikethroughStyle {
445                thickness: 1.0.into(),
446                color: match s {
447                    SemanticTokenColorOverride::InheritForeground(true) => highlight.color,
448                    SemanticTokenColorOverride::InheritForeground(false) => None,
449                    SemanticTokenColorOverride::Replace(c) => Some(c.into()),
450                },
451            },
452        );
453    }
454    Some(highlight)
455}
456
457#[cfg(test)]
458mod tests {
459    use std::{
460        ops::Range,
461        sync::atomic::{self, AtomicUsize},
462    };
463
464    use futures::StreamExt as _;
465    use gpui::{
466        AppContext as _, Entity, Focusable as _, HighlightStyle, TestAppContext, UpdateGlobal as _,
467    };
468    use language::{
469        Diagnostic, DiagnosticEntry, DiagnosticSet, Language, LanguageAwareStyling, LanguageConfig,
470        LanguageMatcher,
471    };
472    use languages::FakeLspAdapter;
473    use lsp::LanguageServerId;
474    use multi_buffer::{
475        AnchorRangeExt, ExpandExcerptDirection, MultiBuffer, MultiBufferOffset, PathKey,
476    };
477    use project::Project;
478    use rope::{Point, PointUtf16};
479    use serde_json::json;
480    use settings::{
481        GlobalLspSettingsContent, LanguageSettingsContent, SemanticTokenRule, SemanticTokenRules,
482        SemanticTokens, SettingsStore,
483    };
484    use workspace::{MultiWorkspace, WorkspaceHandle as _};
485
486    use crate::{
487        Capability,
488        editor_tests::{init_test, update_test_language_settings},
489        test::{build_editor_with_project, editor_lsp_test_context::EditorLspTestContext},
490    };
491
492    use super::*;
493
494    #[gpui::test]
495    async fn lsp_semantic_tokens_full_capability(cx: &mut TestAppContext) {
496        init_test(cx, |_| {});
497
498        update_test_language_settings(cx, &|language_settings| {
499            language_settings.languages.0.insert(
500                "Rust".into(),
501                LanguageSettingsContent {
502                    semantic_tokens: Some(SemanticTokens::Full),
503                    ..LanguageSettingsContent::default()
504                },
505            );
506        });
507
508        let mut cx = EditorLspTestContext::new_rust(
509            lsp::ServerCapabilities {
510                semantic_tokens_provider: Some(
511                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
512                        lsp::SemanticTokensOptions {
513                            legend: lsp::SemanticTokensLegend {
514                                token_types: vec!["function".into()],
515                                token_modifiers: Vec::new(),
516                            },
517                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
518                            ..lsp::SemanticTokensOptions::default()
519                        },
520                    ),
521                ),
522                ..lsp::ServerCapabilities::default()
523            },
524            cx,
525        )
526        .await;
527
528        let full_counter = Arc::new(AtomicUsize::new(0));
529        let full_counter_clone = full_counter.clone();
530
531        let mut full_request = cx
532            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
533                move |_, _, _| {
534                    full_counter_clone.fetch_add(1, atomic::Ordering::Release);
535                    async move {
536                        Ok(Some(lsp::SemanticTokensResult::Tokens(
537                            lsp::SemanticTokens {
538                                data: vec![
539                                    0, // delta_line
540                                    3, // delta_start
541                                    4, // length
542                                    0, // token_type
543                                    0, // token_modifiers_bitset
544                                ],
545                                // The server isn't capable of deltas, so even though we sent back
546                                // a result ID, the client shouldn't request a delta.
547                                result_id: Some("a".into()),
548                            },
549                        )))
550                    }
551                },
552            );
553
554        cx.set_state("ˇfn main() {}");
555        assert!(full_request.next().await.is_some());
556
557        cx.run_until_parked();
558
559        cx.set_state("ˇfn main() { a }");
560        assert!(full_request.next().await.is_some());
561
562        cx.run_until_parked();
563
564        assert_eq!(
565            extract_semantic_highlights(&cx.editor, &cx),
566            vec![MultiBufferOffset(3)..MultiBufferOffset(7)]
567        );
568
569        assert_eq!(full_counter.load(atomic::Ordering::Acquire), 2);
570    }
571
572    #[gpui::test]
573    async fn lsp_semantic_tokens_dynamic_registration_requeries_open_document(
574        cx: &mut TestAppContext,
575    ) {
576        init_test(cx, |_| {});
577
578        update_test_language_settings(cx, &|language_settings| {
579            language_settings.languages.0.insert(
580                "Rust".into(),
581                LanguageSettingsContent {
582                    semantic_tokens: Some(SemanticTokens::Full),
583                    ..LanguageSettingsContent::default()
584                },
585            );
586        });
587
588        // The server advertises no semantic tokens capability up front; it only
589        // registers `textDocument/semanticTokens` dynamically, after the document
590        // is already open (as Roslyn does).
591        let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
592
593        let full_counter = Arc::new(AtomicUsize::new(0));
594        let _full_request = cx
595            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>({
596                let full_counter = full_counter.clone();
597                move |_, _, _| {
598                    full_counter.fetch_add(1, atomic::Ordering::Release);
599                    async move {
600                        Ok(Some(lsp::SemanticTokensResult::Tokens(
601                            lsp::SemanticTokens {
602                                data: vec![0, 3, 4, 0, 0],
603                                result_id: None,
604                            },
605                        )))
606                    }
607                }
608            });
609
610        cx.set_state("ˇfn main() {}");
611        // Drain the refresh scheduled on open (while no capability exists yet), so a
612        // later request can only come from the dynamic-registration refresh itself.
613        cx.executor().advance_clock(Duration::from_millis(200));
614        cx.run_until_parked();
615        assert_eq!(
616            full_counter.load(atomic::Ordering::Acquire),
617            0,
618            "no semantic tokens should be requested before the capability is registered"
619        );
620        assert!(
621            extract_semantic_highlights(&cx.editor, &cx).is_empty(),
622            "no semantic highlights before the capability is registered"
623        );
624
625        cx.lsp
626            .request::<lsp::request::RegisterCapability>(
627                lsp::RegistrationParams {
628                    registrations: vec![lsp::Registration {
629                        id: "semantic-tokens".to_string(),
630                        method: "textDocument/semanticTokens".to_string(),
631                        register_options: Some(
632                            serde_json::to_value(lsp::SemanticTokensRegistrationOptions {
633                                text_document_registration_options:
634                                    lsp::TextDocumentRegistrationOptions {
635                                        document_selector: None,
636                                    },
637                                semantic_tokens_options: lsp::SemanticTokensOptions {
638                                    legend: lsp::SemanticTokensLegend {
639                                        token_types: vec!["function".into()],
640                                        token_modifiers: Vec::new(),
641                                    },
642                                    full: Some(lsp::SemanticTokensFullOptions::Bool(true)),
643                                    ..lsp::SemanticTokensOptions::default()
644                                },
645                                static_registration_options: lsp::StaticRegistrationOptions {
646                                    id: None,
647                                },
648                            })
649                            .unwrap(),
650                        ),
651                    }],
652                },
653                lsp::DEFAULT_LSP_REQUEST_TIMEOUT,
654            )
655            .await
656            .into_response()
657            .expect("register capability request failed");
658
659        cx.executor().advance_clock(Duration::from_millis(200));
660        cx.run_until_parked();
661        assert!(
662            full_counter.load(atomic::Ordering::Acquire) >= 1,
663            "dynamic registration should re-query semantic tokens for the open document"
664        );
665
666        assert_eq!(
667            extract_semantic_highlights(&cx.editor, &cx),
668            vec![MultiBufferOffset(3)..MultiBufferOffset(7)],
669            "the open document should display semantic tokens after dynamic registration"
670        );
671    }
672
673    #[gpui::test]
674    async fn lsp_semantic_tokens_full_none_result_id(cx: &mut TestAppContext) {
675        init_test(cx, |_| {});
676
677        update_test_language_settings(cx, &|language_settings| {
678            language_settings.languages.0.insert(
679                "Rust".into(),
680                LanguageSettingsContent {
681                    semantic_tokens: Some(SemanticTokens::Full),
682                    ..LanguageSettingsContent::default()
683                },
684            );
685        });
686
687        let mut cx = EditorLspTestContext::new_rust(
688            lsp::ServerCapabilities {
689                semantic_tokens_provider: Some(
690                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
691                        lsp::SemanticTokensOptions {
692                            legend: lsp::SemanticTokensLegend {
693                                token_types: vec!["function".into()],
694                                token_modifiers: Vec::new(),
695                            },
696                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: Some(true) }),
697                            ..lsp::SemanticTokensOptions::default()
698                        },
699                    ),
700                ),
701                ..lsp::ServerCapabilities::default()
702            },
703            cx,
704        )
705        .await;
706
707        let full_counter = Arc::new(AtomicUsize::new(0));
708        let full_counter_clone = full_counter.clone();
709
710        let mut full_request = cx
711            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
712                move |_, _, _| {
713                    full_counter_clone.fetch_add(1, atomic::Ordering::Release);
714                    async move {
715                        Ok(Some(lsp::SemanticTokensResult::Tokens(
716                            lsp::SemanticTokens {
717                                data: vec![
718                                    0, // delta_line
719                                    3, // delta_start
720                                    4, // length
721                                    0, // token_type
722                                    0, // token_modifiers_bitset
723                                ],
724                                result_id: None, // Sending back `None` forces the client to not use deltas.
725                            },
726                        )))
727                    }
728                },
729            );
730
731        cx.set_state("ˇfn main() {}");
732        assert!(full_request.next().await.is_some());
733
734        let task = cx.update_editor(|e, _, _| e.semantic_token_state.take_update_task());
735        task.await;
736
737        cx.set_state("ˇfn main() { a }");
738        assert!(full_request.next().await.is_some());
739
740        let task = cx.update_editor(|e, _, _| e.semantic_token_state.take_update_task());
741        task.await;
742        assert_eq!(
743            extract_semantic_highlights(&cx.editor, &cx),
744            vec![MultiBufferOffset(3)..MultiBufferOffset(7)]
745        );
746        assert_eq!(full_counter.load(atomic::Ordering::Acquire), 2);
747    }
748
749    #[gpui::test]
750    async fn lsp_semantic_tokens_delta(cx: &mut TestAppContext) {
751        init_test(cx, |_| {});
752
753        update_test_language_settings(cx, &|language_settings| {
754            language_settings.languages.0.insert(
755                "Rust".into(),
756                LanguageSettingsContent {
757                    semantic_tokens: Some(SemanticTokens::Full),
758                    ..LanguageSettingsContent::default()
759                },
760            );
761        });
762
763        let mut cx = EditorLspTestContext::new_rust(
764            lsp::ServerCapabilities {
765                semantic_tokens_provider: Some(
766                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
767                        lsp::SemanticTokensOptions {
768                            legend: lsp::SemanticTokensLegend {
769                                token_types: vec!["function".into()],
770                                token_modifiers: Vec::new(),
771                            },
772                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: Some(true) }),
773                            ..lsp::SemanticTokensOptions::default()
774                        },
775                    ),
776                ),
777                ..lsp::ServerCapabilities::default()
778            },
779            cx,
780        )
781        .await;
782
783        let full_counter = Arc::new(AtomicUsize::new(0));
784        let full_counter_clone = full_counter.clone();
785        let delta_counter = Arc::new(AtomicUsize::new(0));
786        let delta_counter_clone = delta_counter.clone();
787
788        let mut full_request = cx
789            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
790                move |_, _, _| {
791                    full_counter_clone.fetch_add(1, atomic::Ordering::Release);
792                    async move {
793                        Ok(Some(lsp::SemanticTokensResult::Tokens(
794                            lsp::SemanticTokens {
795                                data: vec![
796                                    0, // delta_line
797                                    3, // delta_start
798                                    4, // length
799                                    0, // token_type
800                                    0, // token_modifiers_bitset
801                                ],
802                                result_id: Some("a".into()),
803                            },
804                        )))
805                    }
806                },
807            );
808
809        let mut delta_request = cx
810            .set_request_handler::<lsp::request::SemanticTokensFullDeltaRequest, _, _>(
811                move |_, params, _| {
812                    delta_counter_clone.fetch_add(1, atomic::Ordering::Release);
813                    assert_eq!(params.previous_result_id, "a");
814                    async move {
815                        Ok(Some(lsp::SemanticTokensFullDeltaResult::TokensDelta(
816                            lsp::SemanticTokensDelta {
817                                edits: Vec::new(),
818                                result_id: Some("b".into()),
819                            },
820                        )))
821                    }
822                },
823            );
824
825        // Initial request, for the empty buffer.
826        cx.set_state("ˇfn main() {}");
827        assert!(full_request.next().await.is_some());
828        let task = cx.update_editor(|e, _, _| e.semantic_token_state.take_update_task());
829        task.await;
830
831        cx.set_state("ˇfn main() { a }");
832        assert!(delta_request.next().await.is_some());
833        let task = cx.update_editor(|e, _, _| e.semantic_token_state.take_update_task());
834        task.await;
835
836        assert_eq!(
837            extract_semantic_highlights(&cx.editor, &cx),
838            vec![MultiBufferOffset(3)..MultiBufferOffset(7)]
839        );
840
841        assert_eq!(full_counter.load(atomic::Ordering::Acquire), 1);
842        assert_eq!(delta_counter.load(atomic::Ordering::Acquire), 1);
843    }
844
845    #[gpui::test]
846    async fn lsp_semantic_tokens_multiserver_full(cx: &mut TestAppContext) {
847        init_test(cx, |_| {});
848
849        update_test_language_settings(cx, &|language_settings| {
850            language_settings.languages.0.insert(
851                "TOML".into(),
852                LanguageSettingsContent {
853                    semantic_tokens: Some(SemanticTokens::Full),
854                    ..LanguageSettingsContent::default()
855                },
856            );
857        });
858
859        let toml_language = Arc::new(Language::new(
860            LanguageConfig {
861                name: "TOML".into(),
862                matcher: (LanguageMatcher {
863                    path_suffixes: vec!["toml".into()],
864                    ..LanguageMatcher::default()
865                })
866                .into(),
867                ..LanguageConfig::default()
868            },
869            None,
870        ));
871
872        // We have 2 language servers for TOML in this test.
873        let toml_legend_1 = lsp::SemanticTokensLegend {
874            token_types: vec!["property".into()],
875            token_modifiers: Vec::new(),
876        };
877        let toml_legend_2 = lsp::SemanticTokensLegend {
878            token_types: vec!["number".into()],
879            token_modifiers: Vec::new(),
880        };
881
882        let app_state = cx.update(workspace::AppState::test);
883
884        cx.update(|cx| {
885            assets::Assets.load_test_fonts(cx);
886            crate::init(cx);
887            workspace::init(app_state.clone(), cx);
888        });
889
890        let project = Project::test(app_state.fs.clone(), [], cx).await;
891        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
892
893        let full_counter_toml_1 = Arc::new(AtomicUsize::new(0));
894        let full_counter_toml_1_clone = full_counter_toml_1.clone();
895        let full_counter_toml_2 = Arc::new(AtomicUsize::new(0));
896        let full_counter_toml_2_clone = full_counter_toml_2.clone();
897
898        let mut toml_server_1 = language_registry.register_fake_lsp(
899            toml_language.name(),
900            FakeLspAdapter {
901                name: "toml1",
902                capabilities: lsp::ServerCapabilities {
903                    semantic_tokens_provider: Some(
904                        lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
905                            lsp::SemanticTokensOptions {
906                                legend: toml_legend_1,
907                                full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
908                                ..lsp::SemanticTokensOptions::default()
909                            },
910                        ),
911                    ),
912                    ..lsp::ServerCapabilities::default()
913                },
914                initializer: Some(Box::new({
915                    let full_counter_toml_1_clone = full_counter_toml_1_clone.clone();
916                    move |fake_server| {
917                        let full_counter = full_counter_toml_1_clone.clone();
918                        fake_server
919                            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
920                                move |_, _| {
921                                    full_counter.fetch_add(1, atomic::Ordering::Release);
922                                    async move {
923                                        Ok(Some(lsp::SemanticTokensResult::Tokens(
924                                            lsp::SemanticTokens {
925                                                // highlight 'a' as a property
926                                                data: vec![
927                                                    0, // delta_line
928                                                    0, // delta_start
929                                                    1, // length
930                                                    0, // token_type
931                                                    0, // token_modifiers_bitset
932                                                ],
933                                                result_id: Some("a".into()),
934                                            },
935                                        )))
936                                    }
937                                },
938                            );
939                    }
940                })),
941                ..FakeLspAdapter::default()
942            },
943        );
944        let mut toml_server_2 = language_registry.register_fake_lsp(
945            toml_language.name(),
946            FakeLspAdapter {
947                name: "toml2",
948                capabilities: lsp::ServerCapabilities {
949                    semantic_tokens_provider: Some(
950                        lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
951                            lsp::SemanticTokensOptions {
952                                legend: toml_legend_2,
953                                full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
954                                ..lsp::SemanticTokensOptions::default()
955                            },
956                        ),
957                    ),
958                    ..lsp::ServerCapabilities::default()
959                },
960                initializer: Some(Box::new({
961                    let full_counter_toml_2_clone = full_counter_toml_2_clone.clone();
962                    move |fake_server| {
963                        let full_counter = full_counter_toml_2_clone.clone();
964                        fake_server
965                            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
966                                move |_, _| {
967                                    full_counter.fetch_add(1, atomic::Ordering::Release);
968                                    async move {
969                                        Ok(Some(lsp::SemanticTokensResult::Tokens(
970                                            lsp::SemanticTokens {
971                                                // highlight '3' as a literal
972                                                data: vec![
973                                                    0, // delta_line
974                                                    4, // delta_start
975                                                    1, // length
976                                                    0, // token_type
977                                                    0, // token_modifiers_bitset
978                                                ],
979                                                result_id: Some("a".into()),
980                                            },
981                                        )))
982                                    }
983                                },
984                            );
985                    }
986                })),
987                ..FakeLspAdapter::default()
988            },
989        );
990        language_registry.add(toml_language.clone());
991
992        app_state
993            .fs
994            .as_fake()
995            .insert_tree(
996                EditorLspTestContext::root_path(),
997                json!({
998                    ".git": {},
999                    "dir": {
1000                        "foo.toml": "a = 1\nb = 2\n",
1001                    }
1002                }),
1003            )
1004            .await;
1005
1006        let (multi_workspace, cx) =
1007            cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
1008        let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
1009        project
1010            .update(cx, |project, cx| {
1011                project.find_or_create_worktree(EditorLspTestContext::root_path(), true, cx)
1012            })
1013            .await
1014            .unwrap();
1015        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
1016            .await;
1017
1018        let toml_file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
1019        let toml_item = workspace
1020            .update_in(cx, |workspace, window, cx| {
1021                workspace.open_path(toml_file, None, true, window, cx)
1022            })
1023            .await
1024            .expect("Could not open test file");
1025
1026        let editor = cx.update(|_, cx| {
1027            toml_item
1028                .act_as::<Editor>(cx)
1029                .expect("Opened test file wasn't an editor")
1030        });
1031
1032        editor.update_in(cx, |editor, window, cx| {
1033            let nav_history = workspace
1034                .read(cx)
1035                .active_pane()
1036                .read(cx)
1037                .nav_history_for_item(&cx.entity());
1038            editor.set_nav_history(Some(nav_history));
1039            window.focus(&editor.focus_handle(cx), cx)
1040        });
1041
1042        let _toml_server_1 = toml_server_1.next().await.unwrap();
1043        let _toml_server_2 = toml_server_2.next().await.unwrap();
1044
1045        // Trigger semantic tokens.
1046        editor.update_in(cx, |editor, _, cx| {
1047            editor.edit([(MultiBufferOffset(0)..MultiBufferOffset(1), "b")], cx);
1048        });
1049        cx.executor().advance_clock(Duration::from_millis(200));
1050        let task = editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
1051        cx.run_until_parked();
1052        task.await;
1053
1054        assert_eq!(
1055            extract_semantic_highlights(&editor, &cx),
1056            vec![
1057                MultiBufferOffset(0)..MultiBufferOffset(1),
1058                MultiBufferOffset(4)..MultiBufferOffset(5),
1059            ]
1060        );
1061
1062        assert_eq!(full_counter_toml_1.load(atomic::Ordering::Acquire), 1);
1063        assert_eq!(full_counter_toml_2.load(atomic::Ordering::Acquire), 1);
1064    }
1065
1066    #[gpui::test]
1067    async fn lsp_semantic_tokens_multibuffer_part(cx: &mut TestAppContext) {
1068        init_test(cx, |_| {});
1069
1070        update_test_language_settings(cx, &|language_settings| {
1071            language_settings.languages.0.insert(
1072                "TOML".into(),
1073                LanguageSettingsContent {
1074                    semantic_tokens: Some(SemanticTokens::Full),
1075                    ..LanguageSettingsContent::default()
1076                },
1077            );
1078            language_settings.languages.0.insert(
1079                "Rust".into(),
1080                LanguageSettingsContent {
1081                    semantic_tokens: Some(SemanticTokens::Full),
1082                    ..LanguageSettingsContent::default()
1083                },
1084            );
1085        });
1086
1087        let toml_language = Arc::new(Language::new(
1088            LanguageConfig {
1089                name: "TOML".into(),
1090                matcher: (LanguageMatcher {
1091                    path_suffixes: vec!["toml".into()],
1092                    ..LanguageMatcher::default()
1093                })
1094                .into(),
1095                ..LanguageConfig::default()
1096            },
1097            None,
1098        ));
1099        let rust_language = Arc::new(Language::new(
1100            LanguageConfig {
1101                name: "Rust".into(),
1102                matcher: (LanguageMatcher {
1103                    path_suffixes: vec!["rs".into()],
1104                    ..LanguageMatcher::default()
1105                })
1106                .into(),
1107                ..LanguageConfig::default()
1108            },
1109            None,
1110        ));
1111
1112        let toml_legend = lsp::SemanticTokensLegend {
1113            token_types: vec!["property".into()],
1114            token_modifiers: Vec::new(),
1115        };
1116        let rust_legend = lsp::SemanticTokensLegend {
1117            token_types: vec!["constant".into()],
1118            token_modifiers: Vec::new(),
1119        };
1120
1121        let app_state = cx.update(workspace::AppState::test);
1122
1123        cx.update(|cx| {
1124            assets::Assets.load_test_fonts(cx);
1125            crate::init(cx);
1126            workspace::init(app_state.clone(), cx);
1127        });
1128
1129        let project = Project::test(app_state.fs.clone(), [], cx).await;
1130        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1131        let full_counter_toml = Arc::new(AtomicUsize::new(0));
1132        let full_counter_toml_clone = full_counter_toml.clone();
1133
1134        let mut toml_server = language_registry.register_fake_lsp(
1135            toml_language.name(),
1136            FakeLspAdapter {
1137                name: "toml",
1138                capabilities: lsp::ServerCapabilities {
1139                    semantic_tokens_provider: Some(
1140                        lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
1141                            lsp::SemanticTokensOptions {
1142                                legend: toml_legend,
1143                                full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
1144                                ..lsp::SemanticTokensOptions::default()
1145                            },
1146                        ),
1147                    ),
1148                    ..lsp::ServerCapabilities::default()
1149                },
1150                initializer: Some(Box::new({
1151                    let full_counter_toml_clone = full_counter_toml_clone.clone();
1152                    move |fake_server| {
1153                        let full_counter = full_counter_toml_clone.clone();
1154                        fake_server
1155                            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
1156                                move |_, _| {
1157                                    full_counter.fetch_add(1, atomic::Ordering::Release);
1158                                    async move {
1159                                        Ok(Some(lsp::SemanticTokensResult::Tokens(
1160                                            lsp::SemanticTokens {
1161                                                // highlight 'a', 'b', 'c' as properties on lines 0, 1, 2
1162                                                data: vec![
1163                                                    0, // delta_line (line 0)
1164                                                    0, // delta_start
1165                                                    1, // length
1166                                                    0, // token_type
1167                                                    0, // token_modifiers_bitset
1168                                                    1, // delta_line (line 1)
1169                                                    0, // delta_start
1170                                                    1, // length
1171                                                    0, // token_type
1172                                                    0, // token_modifiers_bitset
1173                                                    1, // delta_line (line 2)
1174                                                    0, // delta_start
1175                                                    1, // length
1176                                                    0, // token_type
1177                                                    0, // token_modifiers_bitset
1178                                                ],
1179                                                result_id: Some("a".into()),
1180                                            },
1181                                        )))
1182                                    }
1183                                },
1184                            );
1185                    }
1186                })),
1187                ..FakeLspAdapter::default()
1188            },
1189        );
1190        language_registry.add(toml_language.clone());
1191        let mut rust_server = language_registry.register_fake_lsp(
1192            rust_language.name(),
1193            FakeLspAdapter {
1194                name: "rust",
1195                capabilities: lsp::ServerCapabilities {
1196                    semantic_tokens_provider: Some(
1197                        lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
1198                            lsp::SemanticTokensOptions {
1199                                legend: rust_legend,
1200                                full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
1201                                ..lsp::SemanticTokensOptions::default()
1202                            },
1203                        ),
1204                    ),
1205                    ..lsp::ServerCapabilities::default()
1206                },
1207                ..FakeLspAdapter::default()
1208            },
1209        );
1210        language_registry.add(rust_language.clone());
1211
1212        app_state
1213            .fs
1214            .as_fake()
1215            .insert_tree(
1216                EditorLspTestContext::root_path(),
1217                json!({
1218                    ".git": {},
1219                    "dir": {
1220                        "foo.toml": "a = 1\nb = 2\nc = 3\n",
1221                        "bar.rs": "const c: usize = 3;\n",
1222                    }
1223                }),
1224            )
1225            .await;
1226
1227        let (multi_workspace, cx) =
1228            cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
1229        let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
1230        project
1231            .update(cx, |project, cx| {
1232                project.find_or_create_worktree(EditorLspTestContext::root_path(), true, cx)
1233            })
1234            .await
1235            .unwrap();
1236        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
1237            .await;
1238
1239        let toml_file = cx.read(|cx| workspace.file_project_paths(cx)[1].clone());
1240        let rust_file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
1241        let (toml_item, rust_item) = workspace.update_in(cx, |workspace, window, cx| {
1242            (
1243                workspace.open_path(toml_file, None, true, window, cx),
1244                workspace.open_path(rust_file, None, true, window, cx),
1245            )
1246        });
1247        let toml_item = toml_item.await.expect("Could not open test file");
1248        let rust_item = rust_item.await.expect("Could not open test file");
1249
1250        let (toml_editor, rust_editor) = cx.update(|_, cx| {
1251            (
1252                toml_item
1253                    .act_as::<Editor>(cx)
1254                    .expect("Opened test file wasn't an editor"),
1255                rust_item
1256                    .act_as::<Editor>(cx)
1257                    .expect("Opened test file wasn't an editor"),
1258            )
1259        });
1260        let toml_buffer = cx.read(|cx| {
1261            toml_editor
1262                .read(cx)
1263                .buffer()
1264                .read(cx)
1265                .as_singleton()
1266                .unwrap()
1267        });
1268        let rust_buffer = cx.read(|cx| {
1269            rust_editor
1270                .read(cx)
1271                .buffer()
1272                .read(cx)
1273                .as_singleton()
1274                .unwrap()
1275        });
1276        let multibuffer = cx.new(|cx| {
1277            let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1278            multibuffer.set_excerpts_for_path(
1279                PathKey::sorted(0),
1280                toml_buffer.clone(),
1281                [Point::new(0, 0)..Point::new(0, 4)],
1282                0,
1283                cx,
1284            );
1285            multibuffer.set_excerpts_for_path(
1286                PathKey::sorted(1),
1287                rust_buffer.clone(),
1288                [Point::new(0, 0)..Point::new(0, 4)],
1289                0,
1290                cx,
1291            );
1292            multibuffer
1293        });
1294
1295        let editor = workspace.update_in(cx, |workspace, window, cx| {
1296            let editor = cx.new(|cx| build_editor_with_project(project, multibuffer, window, cx));
1297            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1298            editor
1299        });
1300        editor.update_in(cx, |editor, window, cx| {
1301            let nav_history = workspace
1302                .read(cx)
1303                .active_pane()
1304                .read(cx)
1305                .nav_history_for_item(&cx.entity());
1306            editor.set_nav_history(Some(nav_history));
1307            window.focus(&editor.focus_handle(cx), cx)
1308        });
1309
1310        let _toml_server = toml_server.next().await.unwrap();
1311        let _rust_server = rust_server.next().await.unwrap();
1312
1313        // Initial request.
1314        cx.executor().advance_clock(Duration::from_millis(200));
1315        let task = editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
1316        cx.run_until_parked();
1317        task.await;
1318        assert_eq!(full_counter_toml.load(atomic::Ordering::Acquire), 1);
1319        cx.run_until_parked();
1320
1321        // Initially, excerpt only covers line 0, so only the 'a' token should be highlighted.
1322        // The excerpt content is "a = 1\n" (6 chars), so 'a' is at offset 0.
1323        assert_eq!(
1324            extract_semantic_highlights(&editor, &cx),
1325            vec![MultiBufferOffset(0)..MultiBufferOffset(1)]
1326        );
1327
1328        // Get the excerpt id for the TOML excerpt and expand it down by 2 lines.
1329        let toml_anchor = editor.read_with(cx, |editor, cx| {
1330            editor
1331                .buffer()
1332                .read(cx)
1333                .snapshot(cx)
1334                .anchor_in_excerpt(text::Anchor::min_for_buffer(
1335                    toml_buffer.read(cx).remote_id(),
1336                ))
1337                .unwrap()
1338        });
1339        editor.update_in(cx, |editor, _, cx| {
1340            editor.buffer().update(cx, |buffer, cx| {
1341                buffer.expand_excerpts([toml_anchor], 2, ExpandExcerptDirection::Down, cx);
1342            });
1343        });
1344
1345        // Wait for semantic tokens to be re-fetched after expansion.
1346        cx.executor().advance_clock(Duration::from_millis(200));
1347        let task = editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
1348        cx.run_until_parked();
1349        task.await;
1350
1351        // After expansion, the excerpt covers lines 0-2, so 'a', 'b', 'c' should all be highlighted.
1352        // Content is now "a = 1\nb = 2\nc = 3\n" (18 chars).
1353        // 'a' at offset 0, 'b' at offset 6, 'c' at offset 12.
1354        assert_eq!(
1355            extract_semantic_highlights(&editor, &cx),
1356            vec![
1357                MultiBufferOffset(0)..MultiBufferOffset(1),
1358                MultiBufferOffset(6)..MultiBufferOffset(7),
1359                MultiBufferOffset(12)..MultiBufferOffset(13),
1360            ]
1361        );
1362    }
1363
1364    #[gpui::test]
1365    async fn lsp_semantic_tokens_singleton_opened_from_multibuffer(cx: &mut TestAppContext) {
1366        init_test(cx, |_| {});
1367
1368        update_test_language_settings(cx, &|language_settings| {
1369            language_settings.languages.0.insert(
1370                "Rust".into(),
1371                LanguageSettingsContent {
1372                    semantic_tokens: Some(SemanticTokens::Full),
1373                    ..LanguageSettingsContent::default()
1374                },
1375            );
1376        });
1377
1378        let rust_language = Arc::new(Language::new(
1379            LanguageConfig {
1380                name: "Rust".into(),
1381                matcher: (LanguageMatcher {
1382                    path_suffixes: vec!["rs".into()],
1383                    ..LanguageMatcher::default()
1384                })
1385                .into(),
1386                ..LanguageConfig::default()
1387            },
1388            None,
1389        ));
1390
1391        let rust_legend = lsp::SemanticTokensLegend {
1392            token_types: vec!["function".into()],
1393            token_modifiers: Vec::new(),
1394        };
1395
1396        let app_state = cx.update(workspace::AppState::test);
1397        cx.update(|cx| {
1398            assets::Assets.load_test_fonts(cx);
1399            crate::init(cx);
1400            workspace::init(app_state.clone(), cx);
1401        });
1402
1403        let project = Project::test(app_state.fs.clone(), [], cx).await;
1404        let language_registry = project.read_with(cx, |project, _| project.languages().clone());
1405
1406        let mut rust_server = language_registry.register_fake_lsp(
1407            rust_language.name(),
1408            FakeLspAdapter {
1409                name: "rust",
1410                capabilities: lsp::ServerCapabilities {
1411                    semantic_tokens_provider: Some(
1412                        lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
1413                            lsp::SemanticTokensOptions {
1414                                legend: rust_legend,
1415                                full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
1416                                ..lsp::SemanticTokensOptions::default()
1417                            },
1418                        ),
1419                    ),
1420                    ..lsp::ServerCapabilities::default()
1421                },
1422                initializer: Some(Box::new(move |fake_server| {
1423                    fake_server
1424                        .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
1425                            move |_, _| async move {
1426                                Ok(Some(lsp::SemanticTokensResult::Tokens(
1427                                    lsp::SemanticTokens {
1428                                        data: vec![0, 3, 4, 0, 0],
1429                                        result_id: None,
1430                                    },
1431                                )))
1432                            },
1433                        );
1434                })),
1435                ..FakeLspAdapter::default()
1436            },
1437        );
1438        language_registry.add(rust_language.clone());
1439
1440        // foo.rs must be long enough that autoscroll triggers an actual scroll
1441        // position change when opening from the multibuffer with cursor near
1442        // the end. This reproduces the race: set_visible_line_count spawns a
1443        // task, then autoscroll fires ScrollPositionChanged whose handler
1444        // replaces post_scroll_update with a debounced task that skips
1445        // update_lsp_data for singletons.
1446        let mut foo_content = String::from("fn test() {}\n");
1447        for i in 0..100 {
1448            foo_content.push_str(&format!("fn func_{i}() {{}}\n"));
1449        }
1450
1451        app_state
1452            .fs
1453            .as_fake()
1454            .insert_tree(
1455                EditorLspTestContext::root_path(),
1456                json!({
1457                    ".git": {},
1458                    "bar.rs": "fn main() {}\n",
1459                    "foo.rs": foo_content,
1460                }),
1461            )
1462            .await;
1463
1464        let (multi_workspace, cx) =
1465            cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
1466        let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
1467        project
1468            .update(cx, |project, cx| {
1469                project.find_or_create_worktree(EditorLspTestContext::root_path(), true, cx)
1470            })
1471            .await
1472            .unwrap();
1473        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
1474            .await;
1475
1476        // Open bar.rs as an editor to start the LSP server.
1477        let bar_file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
1478        let bar_item = workspace
1479            .update_in(cx, |workspace, window, cx| {
1480                workspace.open_path(bar_file, None, true, window, cx)
1481            })
1482            .await
1483            .expect("Could not open bar.rs");
1484        let bar_editor = cx.update(|_, cx| {
1485            bar_item
1486                .act_as::<Editor>(cx)
1487                .expect("Opened test file wasn't an editor")
1488        });
1489        let bar_buffer = cx.read(|cx| {
1490            bar_editor
1491                .read(cx)
1492                .buffer()
1493                .read(cx)
1494                .as_singleton()
1495                .unwrap()
1496        });
1497
1498        let _rust_server = rust_server.next().await.unwrap();
1499
1500        cx.executor().advance_clock(Duration::from_millis(200));
1501        let task = bar_editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
1502        cx.run_until_parked();
1503        task.await;
1504        cx.run_until_parked();
1505
1506        assert!(
1507            !extract_semantic_highlights(&bar_editor, &cx).is_empty(),
1508            "bar.rs should have semantic tokens after initial open"
1509        );
1510
1511        // Get foo.rs buffer directly from the project. No editor has ever
1512        // fetched semantic tokens for this buffer.
1513        let foo_file = cx.read(|cx| workspace.file_project_paths(cx)[1].clone());
1514        let foo_buffer = project
1515            .update(cx, |project, cx| project.open_buffer(foo_file, cx))
1516            .await
1517            .expect("Could not open foo.rs buffer");
1518
1519        // Build a multibuffer with both files. The foo.rs excerpt covers a
1520        // range near the end of the file so that opening the singleton will
1521        // autoscroll to a position that requires changing scroll_position.
1522        let multibuffer = cx.new(|cx| {
1523            let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
1524            multibuffer.set_excerpts_for_path(
1525                PathKey::sorted(0),
1526                bar_buffer.clone(),
1527                [Point::new(0, 0)..Point::new(0, 12)],
1528                0,
1529                cx,
1530            );
1531            multibuffer.set_excerpts_for_path(
1532                PathKey::sorted(1),
1533                foo_buffer.clone(),
1534                [Point::new(95, 0)..Point::new(100, 0)],
1535                0,
1536                cx,
1537            );
1538            multibuffer
1539        });
1540
1541        let mb_editor = workspace.update_in(cx, |workspace, window, cx| {
1542            let editor =
1543                cx.new(|cx| build_editor_with_project(project.clone(), multibuffer, window, cx));
1544            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
1545            editor
1546        });
1547        mb_editor.update_in(cx, |editor, window, cx| {
1548            let nav_history = workspace
1549                .read(cx)
1550                .active_pane()
1551                .read(cx)
1552                .nav_history_for_item(&cx.entity());
1553            editor.set_nav_history(Some(nav_history));
1554            window.focus(&editor.focus_handle(cx), cx)
1555        });
1556
1557        // Close bar.rs tab so only the multibuffer remains.
1558        workspace
1559            .update_in(cx, |workspace, window, cx| {
1560                let pane = workspace.active_pane().clone();
1561                pane.update(cx, |pane, cx| {
1562                    pane.close_item_by_id(
1563                        bar_editor.entity_id(),
1564                        workspace::SaveIntent::Skip,
1565                        window,
1566                        cx,
1567                    )
1568                })
1569            })
1570            .await
1571            .ok();
1572
1573        cx.run_until_parked();
1574
1575        // Position cursor in the foo.rs excerpt (near line 95+).
1576        mb_editor.update_in(cx, |editor, window, cx| {
1577            let snapshot = editor.display_snapshot(cx);
1578            let end = snapshot.buffer_snapshot().len();
1579            editor.change_selections(None.into(), window, cx, |s| {
1580                s.select_ranges([end..end]);
1581            });
1582        });
1583
1584        // Open the singleton from the multibuffer. open_buffers_in_workspace
1585        // creates the editor and calls change_selections with autoscroll.
1586        // During render, set_visible_line_count fires first (spawning a task),
1587        // then autoscroll_vertically scrolls to line ~95 which emits
1588        // ScrollPositionChanged, whose handler replaces post_scroll_update.
1589        mb_editor.update_in(cx, |editor, window, cx| {
1590            editor.open_excerpts(&crate::actions::OpenExcerpts, window, cx);
1591        });
1592
1593        cx.run_until_parked();
1594        cx.executor().advance_clock(Duration::from_millis(200));
1595        cx.run_until_parked();
1596
1597        let active_editor = workspace.read_with(cx, |workspace, cx| {
1598            workspace
1599                .active_item(cx)
1600                .and_then(|item| item.act_as::<Editor>(cx))
1601                .expect("Active item should be an editor")
1602        });
1603
1604        assert!(
1605            active_editor.read_with(cx, |editor, cx| editor.buffer().read(cx).is_singleton()),
1606            "Active editor should be a singleton buffer"
1607        );
1608
1609        // Wait for semantic tokens on the singleton.
1610        cx.executor().advance_clock(Duration::from_millis(200));
1611        let task = active_editor.update_in(cx, |e, _, _| e.semantic_token_state.take_update_task());
1612        task.await;
1613        cx.run_until_parked();
1614
1615        let highlights = extract_semantic_highlights(&active_editor, &cx);
1616        assert!(
1617            !highlights.is_empty(),
1618            "Singleton editor opened from multibuffer should have semantic tokens"
1619        );
1620    }
1621
1622    fn extract_semantic_highlights(
1623        editor: &Entity<Editor>,
1624        cx: &TestAppContext,
1625    ) -> Vec<Range<MultiBufferOffset>> {
1626        editor.read_with(cx, |editor, cx| {
1627            let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
1628            editor
1629                .display_map
1630                .read(cx)
1631                .semantic_token_highlights
1632                .iter()
1633                .flat_map(|(_, (v, _))| v.iter())
1634                .map(|highlights| highlights.range.to_offset(&multi_buffer_snapshot))
1635                .collect()
1636        })
1637    }
1638
1639    #[gpui::test]
1640    async fn test_semantic_tokens_rules_changes_restyle_tokens(cx: &mut TestAppContext) {
1641        use gpui::{Hsla, Rgba, UpdateGlobal as _};
1642        use settings::{GlobalLspSettingsContent, SemanticTokenRule};
1643
1644        init_test(cx, |_| {});
1645
1646        update_test_language_settings(cx, &|language_settings| {
1647            language_settings.languages.0.insert(
1648                "Rust".into(),
1649                LanguageSettingsContent {
1650                    semantic_tokens: Some(SemanticTokens::Full),
1651                    ..LanguageSettingsContent::default()
1652                },
1653            );
1654        });
1655
1656        let mut cx = EditorLspTestContext::new_rust(
1657            lsp::ServerCapabilities {
1658                semantic_tokens_provider: Some(
1659                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
1660                        lsp::SemanticTokensOptions {
1661                            legend: lsp::SemanticTokensLegend {
1662                                token_types: Vec::from(["function".into()]),
1663                                token_modifiers: Vec::new(),
1664                            },
1665                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
1666                            ..lsp::SemanticTokensOptions::default()
1667                        },
1668                    ),
1669                ),
1670                ..lsp::ServerCapabilities::default()
1671            },
1672            cx,
1673        )
1674        .await;
1675
1676        let mut full_request = cx
1677            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
1678                move |_, _, _| {
1679                    async move {
1680                        Ok(Some(lsp::SemanticTokensResult::Tokens(
1681                            lsp::SemanticTokens {
1682                                data: vec![
1683                                    0, // delta_line
1684                                    3, // delta_start
1685                                    4, // length
1686                                    0, // token_type (function)
1687                                    0, // token_modifiers_bitset
1688                                ],
1689                                result_id: None,
1690                            },
1691                        )))
1692                    }
1693                },
1694            );
1695
1696        // Trigger initial semantic tokens fetch
1697        cx.set_state("ˇfn main() {}");
1698        full_request.next().await;
1699        cx.run_until_parked();
1700
1701        // Verify initial highlights exist (with no custom color yet)
1702        let initial_ranges = extract_semantic_highlights(&cx.editor, &cx);
1703        assert_eq!(
1704            initial_ranges,
1705            vec![MultiBufferOffset(3)..MultiBufferOffset(7)],
1706            "Should have initial semantic token highlights"
1707        );
1708        let initial_styles = extract_semantic_highlight_styles(&cx.editor, &cx);
1709        assert_eq!(initial_styles.len(), 1, "Should have one highlight style");
1710        // Initial color should be None or theme default (not red or blue)
1711        let initial_color = initial_styles[0].color;
1712
1713        // Set a custom foreground color for function tokens via settings.json
1714        let red_color = Rgba {
1715            r: 1.0,
1716            g: 0.0,
1717            b: 0.0,
1718            a: 1.0,
1719        };
1720        cx.update(|_, cx| {
1721            SettingsStore::update_global(cx, |store, cx| {
1722                store.update_user_settings(cx, |settings| {
1723                    settings.global_lsp_settings = Some(GlobalLspSettingsContent {
1724                        semantic_token_rules: Some(SemanticTokenRules {
1725                            rules: Vec::from([SemanticTokenRule {
1726                                token_type: Some("function".to_string()),
1727                                foreground_color: Some(red_color),
1728                                ..SemanticTokenRule::default()
1729                            }]),
1730                        }),
1731                        ..GlobalLspSettingsContent::default()
1732                    });
1733                });
1734            });
1735        });
1736
1737        // Trigger a refetch by making an edit (which forces semantic tokens update)
1738        cx.set_state("ˇfn main() { }");
1739        full_request.next().await;
1740        cx.run_until_parked();
1741
1742        // Verify the highlights now have the custom red color
1743        let styles_after_settings_change = extract_semantic_highlight_styles(&cx.editor, &cx);
1744        assert_eq!(
1745            styles_after_settings_change.len(),
1746            1,
1747            "Should still have one highlight"
1748        );
1749        assert_eq!(
1750            styles_after_settings_change[0].color,
1751            Some(Hsla::from(red_color)),
1752            "Highlight should have the custom red color from settings.json"
1753        );
1754        assert_ne!(
1755            styles_after_settings_change[0].color, initial_color,
1756            "Color should have changed from initial"
1757        );
1758    }
1759
1760    #[gpui::test]
1761    async fn test_theme_override_changes_restyle_semantic_tokens(cx: &mut TestAppContext) {
1762        use collections::IndexMap;
1763        use gpui::{Hsla, Rgba, UpdateGlobal as _};
1764        use theme_settings::{HighlightStyleContent, ThemeStyleContent};
1765
1766        init_test(cx, |_| {});
1767
1768        update_test_language_settings(cx, &|language_settings| {
1769            language_settings.languages.0.insert(
1770                "Rust".into(),
1771                LanguageSettingsContent {
1772                    semantic_tokens: Some(SemanticTokens::Full),
1773                    ..LanguageSettingsContent::default()
1774                },
1775            );
1776        });
1777
1778        let mut cx = EditorLspTestContext::new_rust(
1779            lsp::ServerCapabilities {
1780                semantic_tokens_provider: Some(
1781                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
1782                        lsp::SemanticTokensOptions {
1783                            legend: lsp::SemanticTokensLegend {
1784                                token_types: Vec::from(["function".into()]),
1785                                token_modifiers: Vec::new(),
1786                            },
1787                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
1788                            ..lsp::SemanticTokensOptions::default()
1789                        },
1790                    ),
1791                ),
1792                ..lsp::ServerCapabilities::default()
1793            },
1794            cx,
1795        )
1796        .await;
1797
1798        let mut full_request = cx
1799            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
1800                move |_, _, _| async move {
1801                    Ok(Some(lsp::SemanticTokensResult::Tokens(
1802                        lsp::SemanticTokens {
1803                            data: vec![
1804                                0, // delta_line
1805                                3, // delta_start
1806                                4, // length
1807                                0, // token_type (function)
1808                                0, // token_modifiers_bitset
1809                            ],
1810                            result_id: None,
1811                        },
1812                    )))
1813                },
1814            );
1815
1816        cx.set_state("ˇfn main() {}");
1817        full_request.next().await;
1818        cx.run_until_parked();
1819
1820        let initial_styles = extract_semantic_highlight_styles(&cx.editor, &cx);
1821        assert_eq!(initial_styles.len(), 1, "Should have one highlight style");
1822        let initial_color = initial_styles[0].color;
1823
1824        // Changing experimental_theme_overrides triggers GlobalTheme reload,
1825        // which fires theme_changed → refresh_semantic_token_highlights.
1826        let red_color: Hsla = Rgba {
1827            r: 1.0,
1828            g: 0.0,
1829            b: 0.0,
1830            a: 1.0,
1831        }
1832        .into();
1833        cx.update(|_, cx| {
1834            SettingsStore::update_global(cx, |store, cx| {
1835                store.update_user_settings(cx, |settings| {
1836                    settings.theme.experimental_theme_overrides = Some(ThemeStyleContent {
1837                        syntax: IndexMap::from_iter([(
1838                            "function".to_string(),
1839                            HighlightStyleContent {
1840                                color: Some("#ff0000".to_string()),
1841                                background_color: None,
1842                                font_style: None,
1843                                font_weight: None,
1844                            },
1845                        )]),
1846                        ..ThemeStyleContent::default()
1847                    });
1848                });
1849            });
1850        });
1851
1852        cx.executor().advance_clock(Duration::from_millis(200));
1853        cx.run_until_parked();
1854
1855        let styles_after_override = extract_semantic_highlight_styles(&cx.editor, &cx);
1856        assert_eq!(styles_after_override.len(), 1);
1857        assert_eq!(
1858            styles_after_override[0].color,
1859            Some(red_color),
1860            "Highlight should have red color from theme override"
1861        );
1862        assert_ne!(
1863            styles_after_override[0].color, initial_color,
1864            "Color should have changed from initial"
1865        );
1866
1867        // Changing the override to a different color also restyles.
1868        let blue_color: Hsla = Rgba {
1869            r: 0.0,
1870            g: 0.0,
1871            b: 1.0,
1872            a: 1.0,
1873        }
1874        .into();
1875        cx.update(|_, cx| {
1876            SettingsStore::update_global(cx, |store, cx| {
1877                store.update_user_settings(cx, |settings| {
1878                    settings.theme.experimental_theme_overrides = Some(ThemeStyleContent {
1879                        syntax: IndexMap::from_iter([(
1880                            "function".to_string(),
1881                            HighlightStyleContent {
1882                                color: Some("#0000ff".to_string()),
1883                                background_color: None,
1884                                font_style: None,
1885                                font_weight: None,
1886                            },
1887                        )]),
1888                        ..ThemeStyleContent::default()
1889                    });
1890                });
1891            });
1892        });
1893
1894        cx.executor().advance_clock(Duration::from_millis(200));
1895        cx.run_until_parked();
1896
1897        let styles_after_second_override = extract_semantic_highlight_styles(&cx.editor, &cx);
1898        assert_eq!(styles_after_second_override.len(), 1);
1899        assert_eq!(
1900            styles_after_second_override[0].color,
1901            Some(blue_color),
1902            "Highlight should have blue color from updated theme override"
1903        );
1904
1905        // Removing overrides reverts to the original theme color.
1906        cx.update(|_, cx| {
1907            SettingsStore::update_global(cx, |store, cx| {
1908                store.update_user_settings(cx, |settings| {
1909                    settings.theme.experimental_theme_overrides = None;
1910                });
1911            });
1912        });
1913
1914        cx.executor().advance_clock(Duration::from_millis(200));
1915        cx.run_until_parked();
1916
1917        let styles_after_clear = extract_semantic_highlight_styles(&cx.editor, &cx);
1918        assert_eq!(styles_after_clear.len(), 1);
1919        assert_eq!(
1920            styles_after_clear[0].color, initial_color,
1921            "Highlight should revert to initial color after clearing overrides"
1922        );
1923    }
1924
1925    #[gpui::test]
1926    async fn test_per_theme_overrides_restyle_semantic_tokens(cx: &mut TestAppContext) {
1927        use collections::IndexMap;
1928        use gpui::{Hsla, Rgba, UpdateGlobal as _};
1929        use theme_settings::{HighlightStyleContent, ThemeStyleContent};
1930        use ui::ActiveTheme as _;
1931
1932        init_test(cx, |_| {});
1933
1934        update_test_language_settings(cx, &|language_settings| {
1935            language_settings.languages.0.insert(
1936                "Rust".into(),
1937                LanguageSettingsContent {
1938                    semantic_tokens: Some(SemanticTokens::Full),
1939                    ..LanguageSettingsContent::default()
1940                },
1941            );
1942        });
1943
1944        let mut cx = EditorLspTestContext::new_rust(
1945            lsp::ServerCapabilities {
1946                semantic_tokens_provider: Some(
1947                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
1948                        lsp::SemanticTokensOptions {
1949                            legend: lsp::SemanticTokensLegend {
1950                                token_types: Vec::from(["function".into()]),
1951                                token_modifiers: Vec::new(),
1952                            },
1953                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
1954                            ..lsp::SemanticTokensOptions::default()
1955                        },
1956                    ),
1957                ),
1958                ..lsp::ServerCapabilities::default()
1959            },
1960            cx,
1961        )
1962        .await;
1963
1964        let mut full_request = cx
1965            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
1966                move |_, _, _| async move {
1967                    Ok(Some(lsp::SemanticTokensResult::Tokens(
1968                        lsp::SemanticTokens {
1969                            data: vec![
1970                                0, // delta_line
1971                                3, // delta_start
1972                                4, // length
1973                                0, // token_type (function)
1974                                0, // token_modifiers_bitset
1975                            ],
1976                            result_id: None,
1977                        },
1978                    )))
1979                },
1980            );
1981
1982        cx.set_state("ˇfn main() {}");
1983        full_request.next().await;
1984        cx.run_until_parked();
1985
1986        let initial_styles = extract_semantic_highlight_styles(&cx.editor, &cx);
1987        assert_eq!(initial_styles.len(), 1, "Should have one highlight style");
1988        let initial_color = initial_styles[0].color;
1989
1990        // Per-theme overrides (theme_overrides keyed by theme name) also go through
1991        // GlobalTheme reload → theme_changed → refresh_semantic_token_highlights.
1992        let theme_name = cx.update(|_, cx| cx.theme().name.to_string());
1993        let green_color: Hsla = Rgba {
1994            r: 0.0,
1995            g: 1.0,
1996            b: 0.0,
1997            a: 1.0,
1998        }
1999        .into();
2000        cx.update(|_, cx| {
2001            SettingsStore::update_global(cx, |store, cx| {
2002                store.update_user_settings(cx, |settings| {
2003                    settings.theme.theme_overrides = collections::HashMap::from_iter([(
2004                        theme_name.clone(),
2005                        ThemeStyleContent {
2006                            syntax: IndexMap::from_iter([(
2007                                "function".to_string(),
2008                                HighlightStyleContent {
2009                                    color: Some("#00ff00".to_string()),
2010                                    background_color: None,
2011                                    font_style: None,
2012                                    font_weight: None,
2013                                },
2014                            )]),
2015                            ..ThemeStyleContent::default()
2016                        },
2017                    )]);
2018                });
2019            });
2020        });
2021
2022        cx.executor().advance_clock(Duration::from_millis(200));
2023        cx.run_until_parked();
2024
2025        let styles_after_override = extract_semantic_highlight_styles(&cx.editor, &cx);
2026        assert_eq!(styles_after_override.len(), 1);
2027        assert_eq!(
2028            styles_after_override[0].color,
2029            Some(green_color),
2030            "Highlight should have green color from per-theme override"
2031        );
2032        assert_ne!(
2033            styles_after_override[0].color, initial_color,
2034            "Color should have changed from initial"
2035        );
2036    }
2037
2038    #[gpui::test]
2039    async fn test_stopping_language_server_clears_semantic_tokens(cx: &mut TestAppContext) {
2040        init_test(cx, |_| {});
2041
2042        update_test_language_settings(cx, &|language_settings| {
2043            language_settings.languages.0.insert(
2044                "Rust".into(),
2045                LanguageSettingsContent {
2046                    semantic_tokens: Some(SemanticTokens::Full),
2047                    ..LanguageSettingsContent::default()
2048                },
2049            );
2050        });
2051
2052        let mut cx = EditorLspTestContext::new_rust(
2053            lsp::ServerCapabilities {
2054                semantic_tokens_provider: Some(
2055                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
2056                        lsp::SemanticTokensOptions {
2057                            legend: lsp::SemanticTokensLegend {
2058                                token_types: vec!["function".into()],
2059                                token_modifiers: Vec::new(),
2060                            },
2061                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
2062                            ..lsp::SemanticTokensOptions::default()
2063                        },
2064                    ),
2065                ),
2066                ..lsp::ServerCapabilities::default()
2067            },
2068            cx,
2069        )
2070        .await;
2071
2072        let mut full_request = cx
2073            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
2074                move |_, _, _| async move {
2075                    Ok(Some(lsp::SemanticTokensResult::Tokens(
2076                        lsp::SemanticTokens {
2077                            data: vec![
2078                                0, // delta_line
2079                                3, // delta_start
2080                                4, // length
2081                                0, // token_type
2082                                0, // token_modifiers_bitset
2083                            ],
2084                            result_id: None,
2085                        },
2086                    )))
2087                },
2088            );
2089
2090        cx.set_state("ˇfn main() {}");
2091        assert!(full_request.next().await.is_some());
2092        cx.run_until_parked();
2093
2094        assert_eq!(
2095            extract_semantic_highlights(&cx.editor, &cx),
2096            vec![MultiBufferOffset(3)..MultiBufferOffset(7)],
2097            "Semantic tokens should be present before stopping the server"
2098        );
2099
2100        cx.update_editor(|editor, _, cx| {
2101            let buffers = editor.buffer.read(cx).all_buffers().into_iter().collect();
2102            editor.project.as_ref().unwrap().update(cx, |project, cx| {
2103                project.stop_language_servers_for_buffers(buffers, HashSet::default(), cx);
2104            })
2105        });
2106        cx.executor().advance_clock(Duration::from_millis(200));
2107        cx.run_until_parked();
2108
2109        assert_eq!(
2110            extract_semantic_highlights(&cx.editor, &cx),
2111            Vec::new(),
2112            "Semantic tokens should be cleared after stopping the server"
2113        );
2114    }
2115
2116    #[gpui::test]
2117    async fn test_disabling_semantic_tokens_setting_clears_highlights(cx: &mut TestAppContext) {
2118        init_test(cx, |_| {});
2119
2120        update_test_language_settings(cx, &|language_settings| {
2121            language_settings.languages.0.insert(
2122                "Rust".into(),
2123                LanguageSettingsContent {
2124                    semantic_tokens: Some(SemanticTokens::Full),
2125                    ..LanguageSettingsContent::default()
2126                },
2127            );
2128        });
2129
2130        let mut cx = EditorLspTestContext::new_rust(
2131            lsp::ServerCapabilities {
2132                semantic_tokens_provider: Some(
2133                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
2134                        lsp::SemanticTokensOptions {
2135                            legend: lsp::SemanticTokensLegend {
2136                                token_types: vec!["function".into()],
2137                                token_modifiers: Vec::new(),
2138                            },
2139                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
2140                            ..lsp::SemanticTokensOptions::default()
2141                        },
2142                    ),
2143                ),
2144                ..lsp::ServerCapabilities::default()
2145            },
2146            cx,
2147        )
2148        .await;
2149
2150        let mut full_request = cx
2151            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
2152                move |_, _, _| async move {
2153                    Ok(Some(lsp::SemanticTokensResult::Tokens(
2154                        lsp::SemanticTokens {
2155                            data: vec![
2156                                0, // delta_line
2157                                3, // delta_start
2158                                4, // length
2159                                0, // token_type
2160                                0, // token_modifiers_bitset
2161                            ],
2162                            result_id: None,
2163                        },
2164                    )))
2165                },
2166            );
2167
2168        cx.set_state("ˇfn main() {}");
2169        assert!(full_request.next().await.is_some());
2170        cx.run_until_parked();
2171
2172        assert_eq!(
2173            extract_semantic_highlights(&cx.editor, &cx),
2174            vec![MultiBufferOffset(3)..MultiBufferOffset(7)],
2175            "Semantic tokens should be present before disabling the setting"
2176        );
2177
2178        update_test_language_settings(&mut cx, &|language_settings| {
2179            language_settings.languages.0.insert(
2180                "Rust".into(),
2181                LanguageSettingsContent {
2182                    semantic_tokens: Some(SemanticTokens::Off),
2183                    ..LanguageSettingsContent::default()
2184                },
2185            );
2186        });
2187        cx.executor().advance_clock(Duration::from_millis(200));
2188        cx.run_until_parked();
2189
2190        assert_eq!(
2191            extract_semantic_highlights(&cx.editor, &cx),
2192            Vec::new(),
2193            "Semantic tokens should be cleared after disabling the setting"
2194        );
2195    }
2196
2197    #[gpui::test]
2198    async fn test_semantic_token_disabling_with_empty_rule(cx: &mut TestAppContext) {
2199        init_test(cx, |_| {});
2200        update_test_language_settings(cx, &|s| {
2201            s.languages.0.insert(
2202                "Rust".into(),
2203                LanguageSettingsContent {
2204                    semantic_tokens: Some(SemanticTokens::Full),
2205                    ..Default::default()
2206                },
2207            );
2208        });
2209
2210        let mut cx = EditorLspTestContext::new_rust(
2211            lsp::ServerCapabilities {
2212                semantic_tokens_provider: Some(
2213                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
2214                        lsp::SemanticTokensOptions {
2215                            legend: lsp::SemanticTokensLegend {
2216                                token_types: vec!["function".into()],
2217                                token_modifiers: vec![],
2218                            },
2219                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
2220                            ..Default::default()
2221                        },
2222                    ),
2223                ),
2224                ..Default::default()
2225            },
2226            cx,
2227        )
2228        .await;
2229
2230        let mut full_request = cx
2231            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
2232                move |_, _, _| async move {
2233                    Ok(Some(lsp::SemanticTokensResult::Tokens(
2234                        lsp::SemanticTokens {
2235                            data: vec![0, 3, 4, 0, 0],
2236                            result_id: None,
2237                        },
2238                    )))
2239                },
2240            );
2241
2242        // Verify it highlights by default
2243        cx.set_state("ˇfn main() {}");
2244        full_request.next().await;
2245        cx.run_until_parked();
2246        assert_eq!(extract_semantic_highlights(&cx.editor, &cx).len(), 1);
2247
2248        // Apply EMPTY rule to disable it
2249        cx.update(|_, cx| {
2250            SettingsStore::update_global(cx, |store, cx| {
2251                store.update_user_settings(cx, |settings| {
2252                    settings.global_lsp_settings = Some(GlobalLspSettingsContent {
2253                        semantic_token_rules: Some(SemanticTokenRules {
2254                            rules: vec![SemanticTokenRule {
2255                                token_type: Some("function".to_string()),
2256                                ..Default::default()
2257                            }],
2258                        }),
2259                        ..Default::default()
2260                    });
2261                });
2262            });
2263        });
2264
2265        cx.set_state("ˇfn main() { }");
2266        full_request.next().await;
2267        cx.run_until_parked();
2268
2269        assert!(
2270            extract_semantic_highlights(&cx.editor, &cx).is_empty(),
2271            "Highlighting should be disabled by empty style setting"
2272        );
2273    }
2274
2275    #[gpui::test]
2276    async fn test_semantic_token_broad_rule_disables_specific_token(cx: &mut TestAppContext) {
2277        init_test(cx, |_| {});
2278        update_test_language_settings(cx, &|s| {
2279            s.languages.0.insert(
2280                "Rust".into(),
2281                LanguageSettingsContent {
2282                    semantic_tokens: Some(SemanticTokens::Full),
2283                    ..Default::default()
2284                },
2285            );
2286        });
2287
2288        let mut cx = EditorLspTestContext::new_rust(
2289            lsp::ServerCapabilities {
2290                semantic_tokens_provider: Some(
2291                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
2292                        lsp::SemanticTokensOptions {
2293                            legend: lsp::SemanticTokensLegend {
2294                                token_types: vec!["comment".into()],
2295                                token_modifiers: vec!["documentation".into()],
2296                            },
2297                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
2298                            ..Default::default()
2299                        },
2300                    ),
2301                ),
2302                ..Default::default()
2303            },
2304            cx,
2305        )
2306        .await;
2307
2308        let mut full_request = cx
2309            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
2310                move |_, _, _| async move {
2311                    Ok(Some(lsp::SemanticTokensResult::Tokens(
2312                        lsp::SemanticTokens {
2313                            data: vec![0, 0, 5, 0, 1], // comment [documentation]
2314                            result_id: None,
2315                        },
2316                    )))
2317                },
2318            );
2319
2320        cx.set_state("ˇ/// d\n");
2321        full_request.next().await;
2322        cx.run_until_parked();
2323        assert_eq!(
2324            extract_semantic_highlights(&cx.editor, &cx).len(),
2325            1,
2326            "Documentation comment should be highlighted"
2327        );
2328
2329        // Apply a BROAD empty rule for "comment" (no modifiers)
2330        cx.update(|_, cx| {
2331            SettingsStore::update_global(cx, |store, cx| {
2332                store.update_user_settings(cx, |settings| {
2333                    settings.global_lsp_settings = Some(GlobalLspSettingsContent {
2334                        semantic_token_rules: Some(SemanticTokenRules {
2335                            rules: vec![SemanticTokenRule {
2336                                token_type: Some("comment".to_string()),
2337                                ..Default::default()
2338                            }],
2339                        }),
2340                        ..Default::default()
2341                    });
2342                });
2343            });
2344        });
2345
2346        cx.set_state("ˇ/// d\n");
2347        full_request.next().await;
2348        cx.run_until_parked();
2349
2350        assert!(
2351            extract_semantic_highlights(&cx.editor, &cx).is_empty(),
2352            "Broad empty rule should disable specific documentation comment"
2353        );
2354    }
2355
2356    #[gpui::test]
2357    async fn test_semantic_token_specific_rule_does_not_disable_broad_token(
2358        cx: &mut TestAppContext,
2359    ) {
2360        use gpui::UpdateGlobal as _;
2361        use settings::{GlobalLspSettingsContent, SemanticTokenRule};
2362
2363        init_test(cx, |_| {});
2364        update_test_language_settings(cx, &|s| {
2365            s.languages.0.insert(
2366                "Rust".into(),
2367                LanguageSettingsContent {
2368                    semantic_tokens: Some(SemanticTokens::Full),
2369                    ..Default::default()
2370                },
2371            );
2372        });
2373
2374        let mut cx = EditorLspTestContext::new_rust(
2375            lsp::ServerCapabilities {
2376                semantic_tokens_provider: Some(
2377                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
2378                        lsp::SemanticTokensOptions {
2379                            legend: lsp::SemanticTokensLegend {
2380                                token_types: vec!["comment".into()],
2381                                token_modifiers: vec!["documentation".into()],
2382                            },
2383                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
2384                            ..Default::default()
2385                        },
2386                    ),
2387                ),
2388                ..Default::default()
2389            },
2390            cx,
2391        )
2392        .await;
2393
2394        let mut full_request = cx
2395            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
2396                move |_, _, _| async move {
2397                    Ok(Some(lsp::SemanticTokensResult::Tokens(
2398                        lsp::SemanticTokens {
2399                            data: vec![
2400                                0, 0, 5, 0, 1, // comment [documentation]
2401                                1, 0, 5, 0, 0, // normal comment
2402                            ],
2403                            result_id: None,
2404                        },
2405                    )))
2406                },
2407            );
2408
2409        cx.set_state("ˇ/// d\n// n\n");
2410        full_request.next().await;
2411        cx.run_until_parked();
2412        assert_eq!(
2413            extract_semantic_highlights(&cx.editor, &cx).len(),
2414            2,
2415            "Both documentation and normal comments should be highlighted initially"
2416        );
2417
2418        // Apply a SPECIFIC empty rule for documentation only
2419        cx.update(|_, cx| {
2420            SettingsStore::update_global(cx, |store, cx| {
2421                store.update_user_settings(cx, |settings| {
2422                    settings.global_lsp_settings = Some(GlobalLspSettingsContent {
2423                        semantic_token_rules: Some(SemanticTokenRules {
2424                            rules: vec![SemanticTokenRule {
2425                                token_type: Some("comment".to_string()),
2426                                token_modifiers: vec!["documentation".to_string()],
2427                                ..Default::default()
2428                            }],
2429                        }),
2430                        ..Default::default()
2431                    });
2432                });
2433            });
2434        });
2435
2436        cx.set_state("ˇ/// d\n// n\n");
2437        full_request.next().await;
2438        cx.run_until_parked();
2439
2440        assert_eq!(
2441            extract_semantic_highlights(&cx.editor, &cx).len(),
2442            1,
2443            "Normal comment should still be highlighted (matched by default rule)"
2444        );
2445    }
2446
2447    #[gpui::test]
2448    async fn test_diagnostics_visible_when_semantic_token_set_to_full(cx: &mut TestAppContext) {
2449        init_test(cx, |_| {});
2450
2451        update_test_language_settings(cx, &|language_settings| {
2452            language_settings.languages.0.insert(
2453                "Rust".into(),
2454                LanguageSettingsContent {
2455                    semantic_tokens: Some(SemanticTokens::Full),
2456                    ..LanguageSettingsContent::default()
2457                },
2458            );
2459        });
2460
2461        let mut cx = EditorLspTestContext::new_rust(
2462            lsp::ServerCapabilities {
2463                semantic_tokens_provider: Some(
2464                    lsp::SemanticTokensServerCapabilities::SemanticTokensOptions(
2465                        lsp::SemanticTokensOptions {
2466                            legend: lsp::SemanticTokensLegend {
2467                                token_types: vec!["function".into()],
2468                                token_modifiers: Vec::new(),
2469                            },
2470                            full: Some(lsp::SemanticTokensFullOptions::Delta { delta: None }),
2471                            ..lsp::SemanticTokensOptions::default()
2472                        },
2473                    ),
2474                ),
2475                ..lsp::ServerCapabilities::default()
2476            },
2477            cx,
2478        )
2479        .await;
2480
2481        let mut full_request = cx
2482            .set_request_handler::<lsp::request::SemanticTokensFullRequest, _, _>(
2483                move |_, _, _| {
2484                    async move {
2485                        Ok(Some(lsp::SemanticTokensResult::Tokens(
2486                            lsp::SemanticTokens {
2487                                data: vec![
2488                                    0, // delta_line
2489                                    3, // delta_start
2490                                    4, // length
2491                                    0, // token_type
2492                                    0, // token_modifiers_bitset
2493                                ],
2494                                result_id: Some("a".into()),
2495                            },
2496                        )))
2497                    }
2498                },
2499            );
2500
2501        cx.set_state("ˇfn main() {}");
2502        assert!(full_request.next().await.is_some());
2503
2504        let task = cx.update_editor(|e, _, _| e.semantic_token_state.take_update_task());
2505        task.await;
2506
2507        cx.update_buffer(|buffer, cx| {
2508            buffer.update_diagnostics(
2509                LanguageServerId(0),
2510                DiagnosticSet::new(
2511                    [DiagnosticEntry {
2512                        range: PointUtf16::new(0, 3)..PointUtf16::new(0, 7),
2513                        diagnostic: Diagnostic {
2514                            severity: lsp::DiagnosticSeverity::ERROR,
2515                            group_id: 1,
2516                            message: "unused function".into(),
2517                            ..Default::default()
2518                        },
2519                    }],
2520                    buffer,
2521                ),
2522                cx,
2523            )
2524        });
2525
2526        cx.run_until_parked();
2527        let chunks = cx.update_editor(|editor, window, cx| {
2528            editor
2529                .snapshot(window, cx)
2530                .display_snapshot
2531                .chunks(
2532                    crate::display_map::DisplayRow(0)..crate::display_map::DisplayRow(1),
2533                    LanguageAwareStyling {
2534                        tree_sitter: false,
2535                        diagnostics: true,
2536                    },
2537                    crate::HighlightStyles::default(),
2538                )
2539                .map(|chunk| {
2540                    (
2541                        chunk.text.to_string(),
2542                        chunk.diagnostic_severity,
2543                        chunk.highlight_style,
2544                    )
2545                })
2546                .collect::<Vec<_>>()
2547        });
2548
2549        assert_eq!(
2550            extract_semantic_highlights(&cx.editor, &cx),
2551            vec![MultiBufferOffset(3)..MultiBufferOffset(7)]
2552        );
2553
2554        assert!(
2555            chunks.iter().any(
2556                |(text, severity, style): &(
2557                    String,
2558                    Option<lsp::DiagnosticSeverity>,
2559                    Option<gpui::HighlightStyle>
2560                )| {
2561                    text == "main"
2562                        && *severity == Some(lsp::DiagnosticSeverity::ERROR)
2563                        && style.is_some()
2564                }
2565            ),
2566            "expected 'main' chunk to have both diagnostic and semantic styling: {:?}",
2567            chunks
2568        );
2569    }
2570
2571    fn extract_semantic_highlight_styles(
2572        editor: &Entity<Editor>,
2573        cx: &TestAppContext,
2574    ) -> Vec<HighlightStyle> {
2575        editor.read_with(cx, |editor, cx| {
2576            editor
2577                .display_map
2578                .read(cx)
2579                .semantic_token_highlights
2580                .iter()
2581                .flat_map(|(_, (v, interner))| {
2582                    v.iter().map(|highlights| interner[highlights.style])
2583                })
2584                .collect()
2585        })
2586    }
2587}
2588
Served at tenant.openagents/omega Member data and write actions are omitted.