extension_lsp_adapter.rs

  1use std::ops::Range;
  2use std::path::PathBuf;
  3use std::pin::Pin;
  4use std::sync::Arc;
  5use std::{any::Any, sync::OnceLock};
  6
  7use anyhow::{Context as _, Result};
  8use async_trait::async_trait;
  9use collections::{HashMap, HashSet};
 10use extension::{Extension, ExtensionLanguageServerProxy, WorktreeDelegate};
 11use fs::Fs;
 12use futures::{Future, FutureExt, future::join_all};
 13use gpui::{App, AppContext, AsyncApp, Task};
 14use language::{
 15    BinaryStatus, CodeLabel, HighlightId, Language, LanguageName, LspAdapter, LspAdapterDelegate,
 16    Toolchain,
 17};
 18use lsp::{
 19    CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions, LanguageServerId,
 20    LanguageServerName, LanguageServerSelector,
 21};
 22use serde::Serialize;
 23use serde_json::Value;
 24use util::{ResultExt, fs::make_file_executable, maybe};
 25
 26use crate::{LanguageServerRegistryProxy, LspAccess};
 27
 28/// An adapter that allows an [`LspAdapterDelegate`] to be used as a [`WorktreeDelegate`].
 29struct WorktreeDelegateAdapter(pub Arc<dyn LspAdapterDelegate>);
 30
 31#[async_trait]
 32impl WorktreeDelegate for WorktreeDelegateAdapter {
 33    fn id(&self) -> u64 {
 34        self.0.worktree_id().to_proto()
 35    }
 36
 37    fn root_path(&self) -> String {
 38        self.0.worktree_root_path().to_string_lossy().to_string()
 39    }
 40
 41    async fn read_text_file(&self, path: PathBuf) -> Result<String> {
 42        self.0.read_text_file(path).await
 43    }
 44
 45    async fn which(&self, binary_name: String) -> Option<String> {
 46        self.0
 47            .which(binary_name.as_ref())
 48            .await
 49            .map(|path| path.to_string_lossy().to_string())
 50    }
 51
 52    async fn shell_env(&self) -> Vec<(String, String)> {
 53        self.0.shell_env().await.into_iter().collect()
 54    }
 55}
 56
 57impl ExtensionLanguageServerProxy for LanguageServerRegistryProxy {
 58    fn register_language_server(
 59        &self,
 60        extension: Arc<dyn Extension>,
 61        language_server_name: LanguageServerName,
 62        language: LanguageName,
 63    ) {
 64        let language_server_id = Arc::new(OnceLock::new());
 65        self.language_server_ids
 66            .write()
 67            .unwrap()
 68            .insert(language_server_name.clone(), language_server_id.clone());
 69        self.language_registry.register_lsp_adapter(
 70            language.clone(),
 71            Arc::new(ExtensionLspAdapter::new(
 72                extension,
 73                language_server_name,
 74                language,
 75                language_server_id,
 76            )),
 77        );
 78    }
 79
 80    fn remove_language_server(
 81        &self,
 82        language: &LanguageName,
 83        language_server_name: &LanguageServerName,
 84        cx: &mut App,
 85    ) -> Task<Result<()>> {
 86        self.language_registry
 87            .remove_lsp_adapter(language, language_server_name);
 88
 89        let mut tasks = Vec::new();
 90        match &self.lsp_access {
 91            LspAccess::ViaLspStore(lsp_store) => lsp_store.update(cx, |lsp_store, cx| {
 92                let stop_task = lsp_store.stop_language_servers_for_buffers(
 93                    Vec::new(),
 94                    HashSet::from_iter([LanguageServerSelector::Name(
 95                        language_server_name.clone(),
 96                    )]),
 97                    cx,
 98                );
 99                tasks.push(stop_task);
100            }),
101            LspAccess::ViaWorkspaces(lsp_store_provider) => {
102                if let Ok(lsp_stores) = lsp_store_provider(cx) {
103                    for lsp_store in lsp_stores {
104                        lsp_store.update(cx, |lsp_store, cx| {
105                            let stop_task = lsp_store.stop_language_servers_for_buffers(
106                                Vec::new(),
107                                HashSet::from_iter([LanguageServerSelector::Name(
108                                    language_server_name.clone(),
109                                )]),
110                                cx,
111                            );
112                            tasks.push(stop_task);
113                        });
114                    }
115                }
116            }
117            LspAccess::Noop => {}
118        }
119
120        cx.background_spawn(async move {
121            let results = join_all(tasks).await;
122            for result in results {
123                result?;
124            }
125            Ok(())
126        })
127    }
128
129    fn update_language_server_status(
130        &self,
131        language_server_name: LanguageServerName,
132        status: BinaryStatus,
133    ) {
134        if let Some(id) = self
135            .language_server_ids
136            .read()
137            .unwrap()
138            .get(&language_server_name)
139            && let Some(&id) = id.get()
140        {
141            self.language_registry
142                .update_lsp_binary_status(id, language_server_name, status);
143        }
144    }
145}
146
147struct ExtensionLspAdapter {
148    extension: Arc<dyn Extension>,
149    language_server_name: LanguageServerName,
150    language_name: LanguageName,
151    language_server_id: Arc<OnceLock<LanguageServerId>>,
152}
153
154impl ExtensionLspAdapter {
155    fn new(
156        extension: Arc<dyn Extension>,
157        language_server_name: LanguageServerName,
158        language_name: LanguageName,
159        language_server_id: Arc<OnceLock<LanguageServerId>>,
160    ) -> Self {
161        Self {
162            extension,
163            language_server_name,
164            language_name,
165            language_server_id,
166        }
167    }
168}
169
170#[async_trait(?Send)]
171impl LspAdapter for ExtensionLspAdapter {
172    fn name(&self) -> LanguageServerName {
173        self.language_server_name.clone()
174    }
175
176    fn get_language_server_command<'a>(
177        self: Arc<Self>,
178        delegate: Arc<dyn LspAdapterDelegate>,
179        _: Option<Toolchain>,
180        _: LanguageServerBinaryOptions,
181        _: futures::lock::MutexGuard<'a, Option<LanguageServerBinary>>,
182        language_server_id: LanguageServerId,
183        _: &'a mut AsyncApp,
184    ) -> Pin<Box<dyn 'a + Future<Output = Result<LanguageServerBinary>>>> {
185        async move {
186            let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
187            let command = self
188                .extension
189                .language_server_command(
190                    self.language_server_name.clone(),
191                    self.language_name.clone(),
192                    delegate,
193                )
194                .await?;
195
196            let path = self.extension.path_from_extension(command.command.as_ref());
197
198            // TODO: This should now be done via the `zed::make_file_executable` function in
199            // Zed extension API, but we're leaving these existing usages in place temporarily
200            // to avoid any compatibility issues between Zed and the extension versions.
201            //
202            // We can remove once the following extension versions no longer see any use:
203            // - toml@0.0.2
204            // - zig@0.0.1
205            if ["toml", "zig"].contains(&self.extension.manifest().id.as_ref())
206                && path.starts_with(&self.extension.work_dir())
207            {
208                make_file_executable(&path)
209                    .await
210                    .context("failed to set file permissions")?;
211            }
212            self.language_server_id
213                .set(language_server_id)
214                .ok()
215                .context("failed to set language server id")?;
216
217            Ok(LanguageServerBinary {
218                path,
219                arguments: command.args.into_iter().map(|arg| arg.into()).collect(),
220                env: Some(command.env.into_iter().collect()),
221            })
222        }
223        .boxed_local()
224    }
225
226    async fn fetch_latest_server_version(
227        &self,
228        _: &dyn LspAdapterDelegate,
229    ) -> Result<Box<dyn 'static + Send + Any>> {
230        unreachable!("get_language_server_command is overridden")
231    }
232
233    async fn fetch_server_binary(
234        &self,
235        _: Box<dyn 'static + Send + Any>,
236        _: PathBuf,
237        _: &dyn LspAdapterDelegate,
238    ) -> Result<LanguageServerBinary> {
239        unreachable!("get_language_server_command is overridden")
240    }
241
242    async fn cached_server_binary(
243        &self,
244        _: PathBuf,
245        _: &dyn LspAdapterDelegate,
246    ) -> Option<LanguageServerBinary> {
247        unreachable!("get_language_server_command is overridden")
248    }
249
250    fn code_action_kinds(&self) -> Option<Vec<CodeActionKind>> {
251        let code_action_kinds = self
252            .extension
253            .manifest()
254            .language_servers
255            .get(&self.language_server_name)
256            .and_then(|server| server.code_action_kinds.clone());
257
258        code_action_kinds.or(Some(vec![
259            CodeActionKind::EMPTY,
260            CodeActionKind::QUICKFIX,
261            CodeActionKind::REFACTOR,
262            CodeActionKind::REFACTOR_EXTRACT,
263            CodeActionKind::SOURCE,
264        ]))
265    }
266
267    fn language_ids(&self) -> HashMap<LanguageName, String> {
268        // TODO: The language IDs can be provided via the language server options
269        // in `extension.toml now but we're leaving these existing usages in place temporarily
270        // to avoid any compatibility issues between Zed and the extension versions.
271        //
272        // We can remove once the following extension versions no longer see any use:
273        // - php@0.0.1
274        if self.extension.manifest().id.as_ref() == "php" {
275            return HashMap::from_iter([(LanguageName::new("PHP"), "php".into())]);
276        }
277
278        self.extension
279            .manifest()
280            .language_servers
281            .get(&self.language_server_name)
282            .map(|server| server.language_ids.clone())
283            .unwrap_or_default()
284    }
285
286    async fn initialization_options(
287        self: Arc<Self>,
288        _: &dyn Fs,
289        delegate: &Arc<dyn LspAdapterDelegate>,
290    ) -> Result<Option<serde_json::Value>> {
291        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
292        let json_options = self
293            .extension
294            .language_server_initialization_options(
295                self.language_server_name.clone(),
296                self.language_name.clone(),
297                delegate,
298            )
299            .await?;
300        Ok(if let Some(json_options) = json_options {
301            serde_json::from_str(&json_options).with_context(|| {
302                format!("failed to parse initialization_options from extension: {json_options}")
303            })?
304        } else {
305            None
306        })
307    }
308
309    async fn workspace_configuration(
310        self: Arc<Self>,
311        _: &dyn Fs,
312        delegate: &Arc<dyn LspAdapterDelegate>,
313        _: Option<Toolchain>,
314        _cx: &mut AsyncApp,
315    ) -> Result<Value> {
316        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
317        let json_options: Option<String> = self
318            .extension
319            .language_server_workspace_configuration(self.language_server_name.clone(), delegate)
320            .await?;
321        Ok(if let Some(json_options) = json_options {
322            serde_json::from_str(&json_options).with_context(|| {
323                format!("failed to parse workspace_configuration from extension: {json_options}")
324            })?
325        } else {
326            serde_json::json!({})
327        })
328    }
329
330    async fn additional_initialization_options(
331        self: Arc<Self>,
332        target_language_server_id: LanguageServerName,
333        _: &dyn Fs,
334        delegate: &Arc<dyn LspAdapterDelegate>,
335    ) -> Result<Option<serde_json::Value>> {
336        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
337        let json_options: Option<String> = self
338            .extension
339            .language_server_additional_initialization_options(
340                self.language_server_name.clone(),
341                target_language_server_id.clone(),
342                delegate,
343            )
344            .await?;
345        Ok(if let Some(json_options) = json_options {
346            serde_json::from_str(&json_options).with_context(|| {
347                format!(
348                    "failed to parse additional_initialization_options from extension: {json_options}"
349                )
350            })?
351        } else {
352            None
353        })
354    }
355
356    async fn additional_workspace_configuration(
357        self: Arc<Self>,
358        target_language_server_id: LanguageServerName,
359        _: &dyn Fs,
360        delegate: &Arc<dyn LspAdapterDelegate>,
361
362        _cx: &mut AsyncApp,
363    ) -> Result<Option<serde_json::Value>> {
364        let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _;
365        let json_options: Option<String> = self
366            .extension
367            .language_server_additional_workspace_configuration(
368                self.language_server_name.clone(),
369                target_language_server_id.clone(),
370                delegate,
371            )
372            .await?;
373        Ok(if let Some(json_options) = json_options {
374            serde_json::from_str(&json_options).with_context(|| {
375                format!("failed to parse additional_workspace_configuration from extension: {json_options}")
376            })?
377        } else {
378            None
379        })
380    }
381
382    async fn labels_for_completions(
383        self: Arc<Self>,
384        completions: &[lsp::CompletionItem],
385        language: &Arc<Language>,
386    ) -> Result<Vec<Option<CodeLabel>>> {
387        let completions = completions
388            .iter()
389            .cloned()
390            .map(lsp_completion_to_extension)
391            .collect::<Vec<_>>();
392
393        let labels = self
394            .extension
395            .labels_for_completions(self.language_server_name.clone(), completions)
396            .await?;
397
398        Ok(labels_from_extension(labels, language))
399    }
400
401    async fn labels_for_symbols(
402        self: Arc<Self>,
403        symbols: &[(String, lsp::SymbolKind)],
404        language: &Arc<Language>,
405    ) -> Result<Vec<Option<CodeLabel>>> {
406        let symbols = symbols
407            .iter()
408            .cloned()
409            .map(|(name, kind)| extension::Symbol {
410                name,
411                kind: lsp_symbol_kind_to_extension(kind),
412            })
413            .collect::<Vec<_>>();
414
415        let labels = self
416            .extension
417            .labels_for_symbols(self.language_server_name.clone(), symbols)
418            .await?;
419
420        Ok(labels_from_extension(labels, language))
421    }
422}
423
424fn labels_from_extension(
425    labels: Vec<Option<extension::CodeLabel>>,
426    language: &Arc<Language>,
427) -> Vec<Option<CodeLabel>> {
428    labels
429        .into_iter()
430        .map(|label| {
431            let label = label?;
432            let runs = if label.code.is_empty() {
433                Vec::new()
434            } else {
435                language.highlight_text(&label.code.as_str().into(), 0..label.code.len())
436            };
437            build_code_label(&label, &runs, language)
438        })
439        .collect()
440}
441
442fn build_code_label(
443    label: &extension::CodeLabel,
444    parsed_runs: &[(Range<usize>, HighlightId)],
445    language: &Arc<Language>,
446) -> Option<CodeLabel> {
447    let mut text = String::new();
448    let mut runs = vec![];
449
450    for span in &label.spans {
451        match span {
452            extension::CodeLabelSpan::CodeRange(range) => {
453                let code_span = &label.code.get(range.clone())?;
454                let mut input_ix = range.start;
455                let mut output_ix = text.len();
456                for (run_range, id) in parsed_runs {
457                    if run_range.start >= range.end {
458                        break;
459                    }
460                    if run_range.end <= input_ix {
461                        continue;
462                    }
463
464                    if run_range.start > input_ix {
465                        let len = run_range.start - input_ix;
466                        output_ix += len;
467                        input_ix += len;
468                    }
469
470                    let len = range.end.min(run_range.end) - input_ix;
471                    runs.push((output_ix..output_ix + len, *id));
472                    output_ix += len;
473                    input_ix += len;
474                }
475
476                text.push_str(code_span);
477            }
478            extension::CodeLabelSpan::Literal(span) => {
479                let highlight_id = language
480                    .grammar()
481                    .zip(span.highlight_name.as_ref())
482                    .and_then(|(grammar, highlight_name)| {
483                        grammar.highlight_id_for_name(highlight_name)
484                    })
485                    .unwrap_or_default();
486                let ix = text.len();
487                runs.push((ix..ix + span.text.len(), highlight_id));
488                text.push_str(&span.text);
489            }
490        }
491    }
492
493    let filter_range = label.filter_range.clone();
494    text.get(filter_range.clone())?;
495    Some(CodeLabel {
496        text,
497        runs,
498        filter_range,
499    })
500}
501
502fn lsp_completion_to_extension(value: lsp::CompletionItem) -> extension::Completion {
503    extension::Completion {
504        label: value.label,
505        label_details: value
506            .label_details
507            .map(lsp_completion_item_label_details_to_extension),
508        detail: value.detail,
509        kind: value.kind.map(lsp_completion_item_kind_to_extension),
510        insert_text_format: value
511            .insert_text_format
512            .map(lsp_insert_text_format_to_extension),
513    }
514}
515
516fn lsp_completion_item_label_details_to_extension(
517    value: lsp::CompletionItemLabelDetails,
518) -> extension::CompletionLabelDetails {
519    extension::CompletionLabelDetails {
520        detail: value.detail,
521        description: value.description,
522    }
523}
524
525fn lsp_completion_item_kind_to_extension(
526    value: lsp::CompletionItemKind,
527) -> extension::CompletionKind {
528    match value {
529        lsp::CompletionItemKind::TEXT => extension::CompletionKind::Text,
530        lsp::CompletionItemKind::METHOD => extension::CompletionKind::Method,
531        lsp::CompletionItemKind::FUNCTION => extension::CompletionKind::Function,
532        lsp::CompletionItemKind::CONSTRUCTOR => extension::CompletionKind::Constructor,
533        lsp::CompletionItemKind::FIELD => extension::CompletionKind::Field,
534        lsp::CompletionItemKind::VARIABLE => extension::CompletionKind::Variable,
535        lsp::CompletionItemKind::CLASS => extension::CompletionKind::Class,
536        lsp::CompletionItemKind::INTERFACE => extension::CompletionKind::Interface,
537        lsp::CompletionItemKind::MODULE => extension::CompletionKind::Module,
538        lsp::CompletionItemKind::PROPERTY => extension::CompletionKind::Property,
539        lsp::CompletionItemKind::UNIT => extension::CompletionKind::Unit,
540        lsp::CompletionItemKind::VALUE => extension::CompletionKind::Value,
541        lsp::CompletionItemKind::ENUM => extension::CompletionKind::Enum,
542        lsp::CompletionItemKind::KEYWORD => extension::CompletionKind::Keyword,
543        lsp::CompletionItemKind::SNIPPET => extension::CompletionKind::Snippet,
544        lsp::CompletionItemKind::COLOR => extension::CompletionKind::Color,
545        lsp::CompletionItemKind::FILE => extension::CompletionKind::File,
546        lsp::CompletionItemKind::REFERENCE => extension::CompletionKind::Reference,
547        lsp::CompletionItemKind::FOLDER => extension::CompletionKind::Folder,
548        lsp::CompletionItemKind::ENUM_MEMBER => extension::CompletionKind::EnumMember,
549        lsp::CompletionItemKind::CONSTANT => extension::CompletionKind::Constant,
550        lsp::CompletionItemKind::STRUCT => extension::CompletionKind::Struct,
551        lsp::CompletionItemKind::EVENT => extension::CompletionKind::Event,
552        lsp::CompletionItemKind::OPERATOR => extension::CompletionKind::Operator,
553        lsp::CompletionItemKind::TYPE_PARAMETER => extension::CompletionKind::TypeParameter,
554        _ => extension::CompletionKind::Other(extract_int(value)),
555    }
556}
557
558fn lsp_insert_text_format_to_extension(
559    value: lsp::InsertTextFormat,
560) -> extension::InsertTextFormat {
561    match value {
562        lsp::InsertTextFormat::PLAIN_TEXT => extension::InsertTextFormat::PlainText,
563        lsp::InsertTextFormat::SNIPPET => extension::InsertTextFormat::Snippet,
564        _ => extension::InsertTextFormat::Other(extract_int(value)),
565    }
566}
567
568fn lsp_symbol_kind_to_extension(value: lsp::SymbolKind) -> extension::SymbolKind {
569    match value {
570        lsp::SymbolKind::FILE => extension::SymbolKind::File,
571        lsp::SymbolKind::MODULE => extension::SymbolKind::Module,
572        lsp::SymbolKind::NAMESPACE => extension::SymbolKind::Namespace,
573        lsp::SymbolKind::PACKAGE => extension::SymbolKind::Package,
574        lsp::SymbolKind::CLASS => extension::SymbolKind::Class,
575        lsp::SymbolKind::METHOD => extension::SymbolKind::Method,
576        lsp::SymbolKind::PROPERTY => extension::SymbolKind::Property,
577        lsp::SymbolKind::FIELD => extension::SymbolKind::Field,
578        lsp::SymbolKind::CONSTRUCTOR => extension::SymbolKind::Constructor,
579        lsp::SymbolKind::ENUM => extension::SymbolKind::Enum,
580        lsp::SymbolKind::INTERFACE => extension::SymbolKind::Interface,
581        lsp::SymbolKind::FUNCTION => extension::SymbolKind::Function,
582        lsp::SymbolKind::VARIABLE => extension::SymbolKind::Variable,
583        lsp::SymbolKind::CONSTANT => extension::SymbolKind::Constant,
584        lsp::SymbolKind::STRING => extension::SymbolKind::String,
585        lsp::SymbolKind::NUMBER => extension::SymbolKind::Number,
586        lsp::SymbolKind::BOOLEAN => extension::SymbolKind::Boolean,
587        lsp::SymbolKind::ARRAY => extension::SymbolKind::Array,
588        lsp::SymbolKind::OBJECT => extension::SymbolKind::Object,
589        lsp::SymbolKind::KEY => extension::SymbolKind::Key,
590        lsp::SymbolKind::NULL => extension::SymbolKind::Null,
591        lsp::SymbolKind::ENUM_MEMBER => extension::SymbolKind::EnumMember,
592        lsp::SymbolKind::STRUCT => extension::SymbolKind::Struct,
593        lsp::SymbolKind::EVENT => extension::SymbolKind::Event,
594        lsp::SymbolKind::OPERATOR => extension::SymbolKind::Operator,
595        lsp::SymbolKind::TYPE_PARAMETER => extension::SymbolKind::TypeParameter,
596        _ => extension::SymbolKind::Other(extract_int(value)),
597    }
598}
599
600fn extract_int<T: Serialize>(value: T) -> i32 {
601    maybe!({
602        let kind = serde_json::to_value(&value)?;
603        serde_json::from_value(kind)
604    })
605    .log_err()
606    .unwrap_or(-1)
607}
608
609#[test]
610fn test_build_code_label() {
611    use util::test::marked_text_ranges;
612
613    let (code, code_ranges) = marked_text_ranges(
614        "«const» «a»: «fn»(«Bcd»(«Efgh»)) -> «Ijklm» = pqrs.tuv",
615        false,
616    );
617    let code_runs = code_ranges
618        .into_iter()
619        .map(|range| (range, HighlightId(0)))
620        .collect::<Vec<_>>();
621
622    let label = build_code_label(
623        &extension::CodeLabel {
624            spans: vec![
625                extension::CodeLabelSpan::CodeRange(code.find("pqrs").unwrap()..code.len()),
626                extension::CodeLabelSpan::CodeRange(
627                    code.find(": fn").unwrap()..code.find(" = ").unwrap(),
628                ),
629            ],
630            filter_range: 0.."pqrs.tuv".len(),
631            code,
632        },
633        &code_runs,
634        &language::PLAIN_TEXT,
635    )
636    .unwrap();
637
638    let (label_text, label_ranges) =
639        marked_text_ranges("pqrs.tuv: «fn»(«Bcd»(«Efgh»)) -> «Ijklm»", false);
640    let label_runs = label_ranges
641        .into_iter()
642        .map(|range| (range, HighlightId(0)))
643        .collect::<Vec<_>>();
644
645    assert_eq!(
646        label,
647        CodeLabel {
648            text: label_text,
649            runs: label_runs,
650            filter_range: label.filter_range.clone()
651        }
652    )
653}
654
655#[test]
656fn test_build_code_label_with_invalid_ranges() {
657    use util::test::marked_text_ranges;
658
659    let (code, code_ranges) = marked_text_ranges("const «a»: «B» = '🏀'", false);
660    let code_runs = code_ranges
661        .into_iter()
662        .map(|range| (range, HighlightId(0)))
663        .collect::<Vec<_>>();
664
665    // A span uses a code range that is invalid because it starts inside of
666    // a multi-byte character.
667    let label = build_code_label(
668        &extension::CodeLabel {
669            spans: vec![
670                extension::CodeLabelSpan::CodeRange(
671                    code.find('B').unwrap()..code.find(" = ").unwrap(),
672                ),
673                extension::CodeLabelSpan::CodeRange((code.find('🏀').unwrap() + 1)..code.len()),
674            ],
675            filter_range: 0.."B".len(),
676            code,
677        },
678        &code_runs,
679        &language::PLAIN_TEXT,
680    );
681    assert!(label.is_none());
682
683    // Filter range extends beyond actual text
684    let label = build_code_label(
685        &extension::CodeLabel {
686            spans: vec![extension::CodeLabelSpan::Literal(
687                extension::CodeLabelSpanLiteral {
688                    text: "abc".into(),
689                    highlight_name: Some("type".into()),
690                },
691            )],
692            filter_range: 0..5,
693            code: String::new(),
694        },
695        &code_runs,
696        &language::PLAIN_TEXT,
697    );
698    assert!(label.is_none());
699}