extension_store_test.rs

  1use crate::extension_manifest::SchemaVersion;
  2use crate::extension_settings::ExtensionSettings;
  3use crate::{
  4    Event, ExtensionIndex, ExtensionIndexEntry, ExtensionIndexLanguageEntry,
  5    ExtensionIndexThemeEntry, ExtensionManifest, ExtensionStore, GrammarManifestEntry,
  6    RELOAD_DEBOUNCE_DURATION,
  7};
  8use assistant_slash_command::SlashCommandRegistry;
  9use async_compression::futures::bufread::GzipEncoder;
 10use collections::BTreeMap;
 11use fs::{FakeFs, Fs, RealFs};
 12use futures::{io::BufReader, AsyncReadExt, StreamExt};
 13use gpui::{Context, SemanticVersion, TestAppContext};
 14use http::{FakeHttpClient, Response};
 15use indexed_docs::IndexedDocsRegistry;
 16use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
 17use node_runtime::FakeNodeRuntime;
 18use parking_lot::Mutex;
 19use project::{Project, DEFAULT_COMPLETION_CONTEXT};
 20use serde_json::json;
 21use settings::{Settings as _, SettingsStore};
 22use std::{
 23    ffi::OsString,
 24    path::{Path, PathBuf},
 25    sync::Arc,
 26};
 27use theme::ThemeRegistry;
 28use util::test::temp_tree;
 29
 30#[cfg(test)]
 31#[ctor::ctor]
 32fn init_logger() {
 33    if std::env::var("RUST_LOG").is_ok() {
 34        env_logger::init();
 35    }
 36}
 37
 38#[gpui::test]
 39async fn test_extension_store(cx: &mut TestAppContext) {
 40    init_test(cx);
 41
 42    let fs = FakeFs::new(cx.executor());
 43    let http_client = FakeHttpClient::with_200_response();
 44
 45    fs.insert_tree(
 46        "/the-extension-dir",
 47        json!({
 48            "installed": {
 49                "zed-monokai": {
 50                    "extension.json": r#"{
 51                        "id": "zed-monokai",
 52                        "name": "Zed Monokai",
 53                        "version": "2.0.0",
 54                        "themes": {
 55                            "Monokai Dark": "themes/monokai.json",
 56                            "Monokai Light": "themes/monokai.json",
 57                            "Monokai Pro Dark": "themes/monokai-pro.json",
 58                            "Monokai Pro Light": "themes/monokai-pro.json"
 59                        }
 60                    }"#,
 61                    "themes": {
 62                        "monokai.json": r#"{
 63                            "name": "Monokai",
 64                            "author": "Someone",
 65                            "themes": [
 66                                {
 67                                    "name": "Monokai Dark",
 68                                    "appearance": "dark",
 69                                    "style": {}
 70                                },
 71                                {
 72                                    "name": "Monokai Light",
 73                                    "appearance": "light",
 74                                    "style": {}
 75                                }
 76                            ]
 77                        }"#,
 78                        "monokai-pro.json": r#"{
 79                            "name": "Monokai Pro",
 80                            "author": "Someone",
 81                            "themes": [
 82                                {
 83                                    "name": "Monokai Pro Dark",
 84                                    "appearance": "dark",
 85                                    "style": {}
 86                                },
 87                                {
 88                                    "name": "Monokai Pro Light",
 89                                    "appearance": "light",
 90                                    "style": {}
 91                                }
 92                            ]
 93                        }"#,
 94                    }
 95                },
 96                "zed-ruby": {
 97                    "extension.json": r#"{
 98                        "id": "zed-ruby",
 99                        "name": "Zed Ruby",
100                        "version": "1.0.0",
101                        "grammars": {
102                            "ruby": "grammars/ruby.wasm",
103                            "embedded_template": "grammars/embedded_template.wasm"
104                        },
105                        "languages": {
106                            "ruby": "languages/ruby",
107                            "erb": "languages/erb"
108                        }
109                    }"#,
110                    "grammars": {
111                        "ruby.wasm": "",
112                        "embedded_template.wasm": "",
113                    },
114                    "languages": {
115                        "ruby": {
116                            "config.toml": r#"
117                                name = "Ruby"
118                                grammar = "ruby"
119                                path_suffixes = ["rb"]
120                            "#,
121                            "highlights.scm": "",
122                        },
123                        "erb": {
124                            "config.toml": r#"
125                                name = "ERB"
126                                grammar = "embedded_template"
127                                path_suffixes = ["erb"]
128                            "#,
129                            "highlights.scm": "",
130                        }
131                    },
132                }
133            }
134        }),
135    )
136    .await;
137
138    let mut expected_index = ExtensionIndex {
139        extensions: [
140            (
141                "zed-ruby".into(),
142                ExtensionIndexEntry {
143                    manifest: Arc::new(ExtensionManifest {
144                        id: "zed-ruby".into(),
145                        name: "Zed Ruby".into(),
146                        version: "1.0.0".into(),
147                        schema_version: SchemaVersion::ZERO,
148                        description: None,
149                        authors: Vec::new(),
150                        repository: None,
151                        themes: Default::default(),
152                        lib: Default::default(),
153                        languages: vec!["languages/erb".into(), "languages/ruby".into()],
154                        grammars: [
155                            ("embedded_template".into(), GrammarManifestEntry::default()),
156                            ("ruby".into(), GrammarManifestEntry::default()),
157                        ]
158                        .into_iter()
159                        .collect(),
160                        language_servers: BTreeMap::default(),
161                        slash_commands: BTreeMap::default(),
162                        indexed_docs_providers: BTreeMap::default(),
163                    }),
164                    dev: false,
165                },
166            ),
167            (
168                "zed-monokai".into(),
169                ExtensionIndexEntry {
170                    manifest: Arc::new(ExtensionManifest {
171                        id: "zed-monokai".into(),
172                        name: "Zed Monokai".into(),
173                        version: "2.0.0".into(),
174                        schema_version: SchemaVersion::ZERO,
175                        description: None,
176                        authors: vec![],
177                        repository: None,
178                        themes: vec![
179                            "themes/monokai-pro.json".into(),
180                            "themes/monokai.json".into(),
181                        ],
182                        lib: Default::default(),
183                        languages: Default::default(),
184                        grammars: BTreeMap::default(),
185                        language_servers: BTreeMap::default(),
186                        slash_commands: BTreeMap::default(),
187                        indexed_docs_providers: BTreeMap::default(),
188                    }),
189                    dev: false,
190                },
191            ),
192        ]
193        .into_iter()
194        .collect(),
195        languages: [
196            (
197                "ERB".into(),
198                ExtensionIndexLanguageEntry {
199                    extension: "zed-ruby".into(),
200                    path: "languages/erb".into(),
201                    grammar: Some("embedded_template".into()),
202                    matcher: LanguageMatcher {
203                        path_suffixes: vec!["erb".into()],
204                        first_line_pattern: None,
205                    },
206                },
207            ),
208            (
209                "Ruby".into(),
210                ExtensionIndexLanguageEntry {
211                    extension: "zed-ruby".into(),
212                    path: "languages/ruby".into(),
213                    grammar: Some("ruby".into()),
214                    matcher: LanguageMatcher {
215                        path_suffixes: vec!["rb".into()],
216                        first_line_pattern: None,
217                    },
218                },
219            ),
220        ]
221        .into_iter()
222        .collect(),
223        themes: [
224            (
225                "Monokai Dark".into(),
226                ExtensionIndexThemeEntry {
227                    extension: "zed-monokai".into(),
228                    path: "themes/monokai.json".into(),
229                },
230            ),
231            (
232                "Monokai Light".into(),
233                ExtensionIndexThemeEntry {
234                    extension: "zed-monokai".into(),
235                    path: "themes/monokai.json".into(),
236                },
237            ),
238            (
239                "Monokai Pro Dark".into(),
240                ExtensionIndexThemeEntry {
241                    extension: "zed-monokai".into(),
242                    path: "themes/monokai-pro.json".into(),
243                },
244            ),
245            (
246                "Monokai Pro Light".into(),
247                ExtensionIndexThemeEntry {
248                    extension: "zed-monokai".into(),
249                    path: "themes/monokai-pro.json".into(),
250                },
251            ),
252        ]
253        .into_iter()
254        .collect(),
255    };
256
257    let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
258    let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
259    let slash_command_registry = SlashCommandRegistry::new();
260    let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
261    let node_runtime = FakeNodeRuntime::new();
262
263    let store = cx.new_model(|cx| {
264        ExtensionStore::new(
265            PathBuf::from("/the-extension-dir"),
266            None,
267            fs.clone(),
268            http_client.clone(),
269            None,
270            node_runtime.clone(),
271            language_registry.clone(),
272            theme_registry.clone(),
273            slash_command_registry.clone(),
274            indexed_docs_registry.clone(),
275            cx,
276        )
277    });
278
279    cx.executor().advance_clock(super::RELOAD_DEBOUNCE_DURATION);
280    store.read_with(cx, |store, _| {
281        let index = &store.extension_index;
282        assert_eq!(index.extensions, expected_index.extensions);
283        assert_eq!(index.languages, expected_index.languages);
284        assert_eq!(index.themes, expected_index.themes);
285
286        assert_eq!(
287            language_registry.language_names(),
288            ["ERB", "Plain Text", "Ruby"]
289        );
290        assert_eq!(
291            theme_registry.list_names(false),
292            [
293                "Monokai Dark",
294                "Monokai Light",
295                "Monokai Pro Dark",
296                "Monokai Pro Light",
297                "One Dark",
298            ]
299        );
300    });
301
302    fs.insert_tree(
303        "/the-extension-dir/installed/zed-gruvbox",
304        json!({
305            "extension.json": r#"{
306                "id": "zed-gruvbox",
307                "name": "Zed Gruvbox",
308                "version": "1.0.0",
309                "themes": {
310                    "Gruvbox": "themes/gruvbox.json"
311                }
312            }"#,
313            "themes": {
314                "gruvbox.json": r#"{
315                    "name": "Gruvbox",
316                    "author": "Someone Else",
317                    "themes": [
318                        {
319                            "name": "Gruvbox",
320                            "appearance": "dark",
321                            "style": {}
322                        }
323                    ]
324                }"#,
325            }
326        }),
327    )
328    .await;
329
330    expected_index.extensions.insert(
331        "zed-gruvbox".into(),
332        ExtensionIndexEntry {
333            manifest: Arc::new(ExtensionManifest {
334                id: "zed-gruvbox".into(),
335                name: "Zed Gruvbox".into(),
336                version: "1.0.0".into(),
337                schema_version: SchemaVersion::ZERO,
338                description: None,
339                authors: vec![],
340                repository: None,
341                themes: vec!["themes/gruvbox.json".into()],
342                lib: Default::default(),
343                languages: Default::default(),
344                grammars: BTreeMap::default(),
345                language_servers: BTreeMap::default(),
346                slash_commands: BTreeMap::default(),
347                indexed_docs_providers: BTreeMap::default(),
348            }),
349            dev: false,
350        },
351    );
352    expected_index.themes.insert(
353        "Gruvbox".into(),
354        ExtensionIndexThemeEntry {
355            extension: "zed-gruvbox".into(),
356            path: "themes/gruvbox.json".into(),
357        },
358    );
359
360    let _ = store.update(cx, |store, cx| store.reload(None, cx));
361
362    cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION);
363    store.read_with(cx, |store, _| {
364        let index = &store.extension_index;
365        assert_eq!(index.extensions, expected_index.extensions);
366        assert_eq!(index.languages, expected_index.languages);
367        assert_eq!(index.themes, expected_index.themes);
368
369        assert_eq!(
370            theme_registry.list_names(false),
371            [
372                "Gruvbox",
373                "Monokai Dark",
374                "Monokai Light",
375                "Monokai Pro Dark",
376                "Monokai Pro Light",
377                "One Dark",
378            ]
379        );
380    });
381
382    let prev_fs_metadata_call_count = fs.metadata_call_count();
383    let prev_fs_read_dir_call_count = fs.read_dir_call_count();
384
385    // Create new extension store, as if Zed were restarting.
386    drop(store);
387    let store = cx.new_model(|cx| {
388        ExtensionStore::new(
389            PathBuf::from("/the-extension-dir"),
390            None,
391            fs.clone(),
392            http_client.clone(),
393            None,
394            node_runtime.clone(),
395            language_registry.clone(),
396            theme_registry.clone(),
397            slash_command_registry,
398            indexed_docs_registry,
399            cx,
400        )
401    });
402
403    cx.executor().run_until_parked();
404    store.read_with(cx, |store, _| {
405        assert_eq!(store.extension_index, expected_index);
406        assert_eq!(
407            language_registry.language_names(),
408            ["ERB", "Plain Text", "Ruby"]
409        );
410        assert_eq!(
411            language_registry.grammar_names(),
412            ["embedded_template".into(), "ruby".into()]
413        );
414        assert_eq!(
415            theme_registry.list_names(false),
416            [
417                "Gruvbox",
418                "Monokai Dark",
419                "Monokai Light",
420                "Monokai Pro Dark",
421                "Monokai Pro Light",
422                "One Dark",
423            ]
424        );
425
426        // The on-disk manifest limits the number of FS calls that need to be made
427        // on startup.
428        assert_eq!(fs.read_dir_call_count(), prev_fs_read_dir_call_count);
429        assert_eq!(fs.metadata_call_count(), prev_fs_metadata_call_count + 2);
430    });
431
432    store.update(cx, |store, cx| {
433        store.uninstall_extension("zed-ruby".into(), cx)
434    });
435
436    cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION);
437    expected_index.extensions.remove("zed-ruby");
438    expected_index.languages.remove("Ruby");
439    expected_index.languages.remove("ERB");
440
441    store.read_with(cx, |store, _| {
442        assert_eq!(store.extension_index, expected_index);
443        assert_eq!(language_registry.language_names(), ["Plain Text"]);
444        assert_eq!(language_registry.grammar_names(), []);
445    });
446}
447
448#[gpui::test]
449async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) {
450    init_test(cx);
451    cx.executor().allow_parking();
452
453    let root_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
454        .parent()
455        .unwrap()
456        .parent()
457        .unwrap();
458    let cache_dir = root_dir.join("target");
459    let test_extension_id = "test-extension";
460    let test_extension_dir = root_dir.join("extensions").join(test_extension_id);
461
462    let fs = Arc::new(RealFs::default());
463    let extensions_dir = temp_tree(json!({
464        "installed": {},
465        "work": {}
466    }));
467    let project_dir = temp_tree(json!({
468        "test.gleam": ""
469    }));
470
471    let extensions_dir = extensions_dir.path().canonicalize().unwrap();
472    let project_dir = project_dir.path().canonicalize().unwrap();
473
474    let project = Project::test(fs.clone(), [project_dir.as_path()], cx).await;
475
476    let language_registry = project.read_with(cx, |project, _cx| project.languages().clone());
477    let theme_registry = Arc::new(ThemeRegistry::new(Box::new(())));
478    let slash_command_registry = SlashCommandRegistry::new();
479    let indexed_docs_registry = Arc::new(IndexedDocsRegistry::new(cx.executor()));
480    let node_runtime = FakeNodeRuntime::new();
481
482    let mut status_updates = language_registry.language_server_binary_statuses();
483
484    struct FakeLanguageServerVersion {
485        version: String,
486        binary_contents: String,
487        http_request_count: usize,
488    }
489
490    let language_server_version = Arc::new(Mutex::new(FakeLanguageServerVersion {
491        version: "v1.2.3".into(),
492        binary_contents: "the-binary-contents".into(),
493        http_request_count: 0,
494    }));
495
496    let http_client = FakeHttpClient::create({
497        let language_server_version = language_server_version.clone();
498        move |request| {
499            let language_server_version = language_server_version.clone();
500            async move {
501                let version = language_server_version.lock().version.clone();
502                let binary_contents = language_server_version.lock().binary_contents.clone();
503
504                let github_releases_uri = "https://api.github.com/repos/gleam-lang/gleam/releases";
505                let asset_download_uri =
506                    format!("https://fake-download.example.com/gleam-{version}");
507
508                let uri = request.uri().to_string();
509                if uri == github_releases_uri {
510                    language_server_version.lock().http_request_count += 1;
511                    Ok(Response::new(
512                        json!([
513                            {
514                                "tag_name": version,
515                                "prerelease": false,
516                                "tarball_url": "",
517                                "zipball_url": "",
518                                "assets": [
519                                    {
520                                        "name": format!("gleam-{version}-aarch64-apple-darwin.tar.gz"),
521                                        "browser_download_url": asset_download_uri
522                                    },
523                                    {
524                                        "name": format!("gleam-{version}-x86_64-unknown-linux-musl.tar.gz"),
525                                        "browser_download_url": asset_download_uri
526                                    },
527                                    {
528                                        "name": format!("gleam-{version}-aarch64-unknown-linux-musl.tar.gz"),
529                                        "browser_download_url": asset_download_uri
530                                    }
531                                ]
532                            }
533                        ])
534                        .to_string()
535                        .into(),
536                    ))
537                } else if uri == asset_download_uri {
538                    language_server_version.lock().http_request_count += 1;
539                    let mut bytes = Vec::<u8>::new();
540                    let mut archive = async_tar::Builder::new(&mut bytes);
541                    let mut header = async_tar::Header::new_gnu();
542                    header.set_size(binary_contents.len() as u64);
543                    archive
544                        .append_data(&mut header, "gleam", binary_contents.as_bytes())
545                        .await
546                        .unwrap();
547                    archive.into_inner().await.unwrap();
548                    let mut gzipped_bytes = Vec::new();
549                    let mut encoder = GzipEncoder::new(BufReader::new(bytes.as_slice()));
550                    encoder.read_to_end(&mut gzipped_bytes).await.unwrap();
551                    Ok(Response::new(gzipped_bytes.into()))
552                } else {
553                    Ok(Response::builder().status(404).body("not found".into())?)
554                }
555            }
556        }
557    });
558
559    let extension_store = cx.new_model(|cx| {
560        ExtensionStore::new(
561            extensions_dir.clone(),
562            Some(cache_dir),
563            fs.clone(),
564            http_client.clone(),
565            None,
566            node_runtime,
567            language_registry.clone(),
568            theme_registry.clone(),
569            slash_command_registry,
570            indexed_docs_registry,
571            cx,
572        )
573    });
574
575    // Ensure that debounces fire.
576    let mut events = cx.events(&extension_store);
577    let executor = cx.executor();
578    let _task = cx.executor().spawn(async move {
579        while let Some(event) = events.next().await {
580            match event {
581                crate::Event::StartedReloading => {
582                    executor.advance_clock(RELOAD_DEBOUNCE_DURATION);
583                }
584                _ => (),
585            }
586        }
587    });
588
589    extension_store.update(cx, |_, cx| {
590        cx.subscribe(&extension_store, |_, _, event, _| {
591            if matches!(event, Event::ExtensionFailedToLoad(_)) {
592                panic!("extension failed to load");
593            }
594        })
595        .detach();
596    });
597
598    extension_store
599        .update(cx, |store, cx| {
600            store.install_dev_extension(test_extension_dir.clone(), cx)
601        })
602        .await
603        .unwrap();
604
605    let mut fake_servers = language_registry.fake_language_servers("Gleam");
606
607    let buffer = project
608        .update(cx, |project, cx| {
609            project.open_local_buffer(project_dir.join("test.gleam"), cx)
610        })
611        .await
612        .unwrap();
613
614    let fake_server = fake_servers.next().await.unwrap();
615    let expected_server_path =
616        extensions_dir.join(format!("work/{test_extension_id}/gleam-v1.2.3/gleam"));
617    let expected_binary_contents = language_server_version.lock().binary_contents.clone();
618
619    assert_eq!(fake_server.binary.path, expected_server_path);
620    assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);
621    assert_eq!(
622        fs.load(&expected_server_path).await.unwrap(),
623        expected_binary_contents
624    );
625    assert_eq!(language_server_version.lock().http_request_count, 2);
626    assert_eq!(
627        [
628            status_updates.next().await.unwrap(),
629            status_updates.next().await.unwrap(),
630            status_updates.next().await.unwrap(),
631        ],
632        [
633            (
634                LanguageServerName("gleam".into()),
635                LanguageServerBinaryStatus::CheckingForUpdate
636            ),
637            (
638                LanguageServerName("gleam".into()),
639                LanguageServerBinaryStatus::Downloading
640            ),
641            (
642                LanguageServerName("gleam".into()),
643                LanguageServerBinaryStatus::None
644            )
645        ]
646    );
647
648    // The extension creates custom labels for completion items.
649    fake_server.handle_request::<lsp::request::Completion, _, _>(|_, _| async move {
650        Ok(Some(lsp::CompletionResponse::Array(vec![
651            lsp::CompletionItem {
652                label: "foo".into(),
653                kind: Some(lsp::CompletionItemKind::FUNCTION),
654                detail: Some("fn() -> Result(Nil, Error)".into()),
655                ..Default::default()
656            },
657            lsp::CompletionItem {
658                label: "bar.baz".into(),
659                kind: Some(lsp::CompletionItemKind::FUNCTION),
660                detail: Some("fn(List(a)) -> a".into()),
661                ..Default::default()
662            },
663            lsp::CompletionItem {
664                label: "Quux".into(),
665                kind: Some(lsp::CompletionItemKind::CONSTRUCTOR),
666                detail: Some("fn(String) -> T".into()),
667                ..Default::default()
668            },
669            lsp::CompletionItem {
670                label: "my_string".into(),
671                kind: Some(lsp::CompletionItemKind::CONSTANT),
672                detail: Some("String".into()),
673                ..Default::default()
674            },
675        ])))
676    });
677
678    let completion_labels = project
679        .update(cx, |project, cx| {
680            project.completions(&buffer, 0, DEFAULT_COMPLETION_CONTEXT, cx)
681        })
682        .await
683        .unwrap()
684        .into_iter()
685        .map(|c| c.label.text)
686        .collect::<Vec<_>>();
687    assert_eq!(
688        completion_labels,
689        [
690            "foo: fn() -> Result(Nil, Error)".to_string(),
691            "bar.baz: fn(List(a)) -> a".to_string(),
692            "Quux: fn(String) -> T".to_string(),
693            "my_string: String".to_string(),
694        ]
695    );
696
697    // Simulate a new version of the language server being released
698    language_server_version.lock().version = "v2.0.0".into();
699    language_server_version.lock().binary_contents = "the-new-binary-contents".into();
700    language_server_version.lock().http_request_count = 0;
701
702    // Start a new instance of the language server.
703    project.update(cx, |project, cx| {
704        project.restart_language_servers_for_buffers([buffer.clone()], cx)
705    });
706
707    // The extension has cached the binary path, and does not attempt
708    // to reinstall it.
709    let fake_server = fake_servers.next().await.unwrap();
710    assert_eq!(fake_server.binary.path, expected_server_path);
711    assert_eq!(
712        fs.load(&expected_server_path).await.unwrap(),
713        expected_binary_contents
714    );
715    assert_eq!(language_server_version.lock().http_request_count, 0);
716
717    // Reload the extension, clearing its cache.
718    // Start a new instance of the language server.
719    extension_store
720        .update(cx, |store, cx| store.reload(Some("gleam".into()), cx))
721        .await;
722
723    cx.executor().run_until_parked();
724    project.update(cx, |project, cx| {
725        project.restart_language_servers_for_buffers([buffer.clone()], cx)
726    });
727
728    // The extension re-fetches the latest version of the language server.
729    let fake_server = fake_servers.next().await.unwrap();
730    let new_expected_server_path =
731        extensions_dir.join(format!("work/{test_extension_id}/gleam-v2.0.0/gleam"));
732    let expected_binary_contents = language_server_version.lock().binary_contents.clone();
733    assert_eq!(fake_server.binary.path, new_expected_server_path);
734    assert_eq!(fake_server.binary.arguments, [OsString::from("lsp")]);
735    assert_eq!(
736        fs.load(&new_expected_server_path).await.unwrap(),
737        expected_binary_contents
738    );
739
740    // The old language server directory has been cleaned up.
741    assert!(fs.metadata(&expected_server_path).await.unwrap().is_none());
742}
743
744fn init_test(cx: &mut TestAppContext) {
745    cx.update(|cx| {
746        let store = SettingsStore::test(cx);
747        cx.set_global(store);
748        release_channel::init(SemanticVersion::default(), cx);
749        theme::init(theme::LoadThemes::JustBase, cx);
750        Project::init_settings(cx);
751        ExtensionSettings::register(cx);
752        language::init(cx);
753    });
754}