Improve performance of JSON schema creation (#6770)

Piotr Osiewicz and Kirill created

JSON LSP adapter now caches the schema. `workspace_configuration` is
back to being async, and we are also no longer asking for font names
twice while constructing the schema.
Release Notes:
- Improved performance when opening the .json files.

---------

Co-authored-by: Kirill <kirill@zed.dev>

Change summary

crates/language/src/language.rs          |  2 
crates/project/src/project.rs            |  7 -
crates/settings/src/settings_store.rs    |  1 
crates/terminal/src/terminal_settings.rs | 12 +-
crates/theme/src/settings.rs             |  8 +-
crates/zed/src/languages/json.rs         | 87 ++++++++++++++-----------
6 files changed, 63 insertions(+), 54 deletions(-)

Detailed changes

crates/language/src/language.rs 🔗

@@ -342,7 +342,7 @@ pub trait LspAdapter: 'static + Send + Sync {
         None
     }
 
-    fn workspace_configuration(&self, _: &Path, _: &mut AppContext) -> Value {
+    fn workspace_configuration(&self, _workspace_root: &Path, _cx: &mut AppContext) -> Value {
         serde_json::json!({})
     }
 

crates/project/src/project.rs 🔗

@@ -2714,13 +2714,12 @@ impl Project {
                 })?;
 
                 for (adapter, server) in servers {
-                    let workspace_config =
+                    let settings =
                         cx.update(|cx| adapter.workspace_configuration(server.root_path(), cx))?;
+
                     server
                         .notify::<lsp::notification::DidChangeConfiguration>(
-                            lsp::DidChangeConfigurationParams {
-                                settings: workspace_config.clone(),
-                            },
+                            lsp::DidChangeConfigurationParams { settings },
                         )
                         .ok();
                 }

crates/settings/src/settings_store.rs 🔗

@@ -114,6 +114,7 @@ pub trait Settings: 'static + Send + Sync {
 pub struct SettingsJsonSchemaParams<'a> {
     pub staff_mode: bool,
     pub language_names: &'a [String],
+    pub font_names: &'a [String],
 }
 
 /// A set of strongly-typed setting values defined via multiple JSON files.

crates/terminal/src/terminal_settings.rs 🔗

@@ -161,14 +161,14 @@ impl settings::Settings for TerminalSettings {
     }
     fn json_schema(
         generator: &mut SchemaGenerator,
-        _: &SettingsJsonSchemaParams,
-        cx: &AppContext,
+        params: &SettingsJsonSchemaParams,
+        _: &AppContext,
     ) -> RootSchema {
         let mut root_schema = generator.root_schema_for::<Self::FileContent>();
-        let available_fonts = cx
-            .text_system()
-            .all_font_names()
-            .into_iter()
+        let available_fonts = params
+            .font_names
+            .iter()
+            .cloned()
             .map(Value::String)
             .collect();
         let fonts_schema = SchemaObject {

crates/theme/src/settings.rs 🔗

@@ -242,10 +242,10 @@ impl settings::Settings for ThemeSettings {
             ..Default::default()
         };
 
-        let available_fonts = cx
-            .text_system()
-            .all_font_names()
-            .into_iter()
+        let available_fonts = params
+            .font_names
+            .iter()
+            .cloned()
             .map(Value::String)
             .collect();
         let fonts_schema = SchemaObject {

crates/zed/src/languages/json.rs 🔗

@@ -7,14 +7,14 @@ use gpui::AppContext;
 use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
 use lsp::LanguageServerBinary;
 use node_runtime::NodeRuntime;
-use serde_json::json;
+use serde_json::{json, Value};
 use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
 use smol::fs;
 use std::{
     any::Any,
     ffi::OsString,
     path::{Path, PathBuf},
-    sync::Arc,
+    sync::{Arc, OnceLock},
 };
 use util::{paths, ResultExt};
 
@@ -28,11 +28,52 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 pub struct JsonLspAdapter {
     node: Arc<dyn NodeRuntime>,
     languages: Arc<LanguageRegistry>,
+    workspace_config: OnceLock<Value>,
 }
 
 impl JsonLspAdapter {
     pub fn new(node: Arc<dyn NodeRuntime>, languages: Arc<LanguageRegistry>) -> Self {
-        JsonLspAdapter { node, languages }
+        Self {
+            node,
+            languages,
+            workspace_config: Default::default(),
+        }
+    }
+
+    fn get_workspace_config(language_names: Vec<String>, cx: &mut AppContext) -> Value {
+        let action_names = cx.all_action_names();
+        let staff_mode = cx.is_staff();
+
+        let font_names = &cx.text_system().all_font_names();
+        let settings_schema = cx.global::<SettingsStore>().json_schema(
+            &SettingsJsonSchemaParams {
+                language_names: &language_names,
+                staff_mode,
+                font_names,
+            },
+            cx,
+        );
+
+        serde_json::json!({
+            "json": {
+                "format": {
+                    "enable": true,
+                },
+                "schemas": [
+                    {
+                        "fileMatch": [
+                            schema_file_match(&paths::SETTINGS),
+                            &*paths::LOCAL_SETTINGS_RELATIVE_PATH,
+                        ],
+                        "schema": settings_schema,
+                    },
+                    {
+                        "fileMatch": [schema_file_match(&paths::KEYMAP)],
+                        "schema": KeymapFile::generate_json_schema(&action_names),
+                    }
+                ]
+            }
+        })
     }
 }
 
@@ -102,42 +143,10 @@ impl LspAdapter for JsonLspAdapter {
         }))
     }
 
-    fn workspace_configuration(
-        &self,
-        _workspace_root: &Path,
-        cx: &mut AppContext,
-    ) -> serde_json::Value {
-        let action_names = cx.all_action_names();
-        let staff_mode = cx.is_staff();
-        let language_names = &self.languages.language_names();
-        let settings_schema = cx.global::<SettingsStore>().json_schema(
-            &SettingsJsonSchemaParams {
-                language_names,
-                staff_mode,
-            },
-            cx,
-        );
-
-        serde_json::json!({
-            "json": {
-                "format": {
-                    "enable": true,
-                },
-                "schemas": [
-                    {
-                        "fileMatch": [
-                            schema_file_match(&paths::SETTINGS),
-                            &*paths::LOCAL_SETTINGS_RELATIVE_PATH,
-                        ],
-                        "schema": settings_schema,
-                    },
-                    {
-                        "fileMatch": [schema_file_match(&paths::KEYMAP)],
-                        "schema": KeymapFile::generate_json_schema(&action_names),
-                    }
-                ]
-            }
-        })
+    fn workspace_configuration(&self, _workspace_root: &Path, cx: &mut AppContext) -> Value {
+        self.workspace_config
+            .get_or_init(|| Self::get_workspace_config(self.languages.language_names(), cx))
+            .clone()
     }
 
     fn language_ids(&self) -> HashMap<String, String> {