toolchains: Expose raw JSON representation of a toolchain (#20721)

Piotr Osiewicz created

Closes #ISSUE

Release Notes:

- N/A

Change summary

crates/language/src/toolchain.rs      |  2 ++
crates/languages/src/python.rs        |  3 ++-
crates/project/src/toolchain_store.rs | 28 +++++++++++++++++++---------
crates/proto/proto/zed.proto          |  1 +
crates/workspace/src/persistence.rs   | 23 ++++++++++++++---------
5 files changed, 38 insertions(+), 19 deletions(-)

Detailed changes

crates/language/src/toolchain.rs 🔗

@@ -20,6 +20,8 @@ pub struct Toolchain {
     pub name: SharedString,
     pub path: SharedString,
     pub language_name: LanguageName,
+    /// Full toolchain data (including language-specific details)
+    pub as_json: serde_json::Value,
 }
 
 #[async_trait(?Send)]

crates/languages/src/python.rs 🔗

@@ -591,8 +591,9 @@ impl ToolchainLister for PythonToolchainProvider {
                 .into();
                 Some(Toolchain {
                     name,
-                    path: toolchain.executable?.to_str()?.to_owned().into(),
+                    path: toolchain.executable.as_ref()?.to_str()?.to_owned().into(),
                     language_name: LanguageName::new("Python"),
+                    as_json: serde_json::to_value(toolchain).ok()?,
                 })
             })
             .collect();

crates/project/src/toolchain_store.rs 🔗

@@ -1,4 +1,4 @@
-use std::sync::Arc;
+use std::{str::FromStr, sync::Arc};
 
 use anyhow::{bail, Result};
 
@@ -119,6 +119,7 @@ impl ToolchainStore {
             let toolchain = Toolchain {
                 name: toolchain.name.into(),
                 path: toolchain.path.into(),
+                as_json: serde_json::Value::from_str(&toolchain.raw_json)?,
                 language_name,
             };
             let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
@@ -144,6 +145,7 @@ impl ToolchainStore {
             toolchain: toolchain.map(|toolchain| proto::Toolchain {
                 name: toolchain.name.into(),
                 path: toolchain.path.into(),
+                raw_json: toolchain.as_json.to_string(),
             }),
         })
     }
@@ -182,6 +184,7 @@ impl ToolchainStore {
                 .map(|toolchain| proto::Toolchain {
                     name: toolchain.name.to_string(),
                     path: toolchain.path.to_string(),
+                    raw_json: toolchain.as_json.to_string(),
                 })
                 .collect::<Vec<_>>()
         } else {
@@ -352,6 +355,7 @@ impl RemoteToolchainStore {
                     toolchain: Some(proto::Toolchain {
                         name: toolchain.name.into(),
                         path: toolchain.path.into(),
+                        raw_json: toolchain.as_json.to_string(),
                     }),
                 })
                 .await
@@ -383,10 +387,13 @@ impl RemoteToolchainStore {
             let toolchains = response
                 .toolchains
                 .into_iter()
-                .map(|toolchain| Toolchain {
-                    language_name: language_name.clone(),
-                    name: toolchain.name.into(),
-                    path: toolchain.path.into(),
+                .filter_map(|toolchain| {
+                    Some(Toolchain {
+                        language_name: language_name.clone(),
+                        name: toolchain.name.into(),
+                        path: toolchain.path.into(),
+                        as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
+                    })
                 })
                 .collect();
             let groups = response
@@ -421,10 +428,13 @@ impl RemoteToolchainStore {
                 .await
                 .log_err()?;
 
-            response.toolchain.map(|toolchain| Toolchain {
-                language_name: language_name.clone(),
-                name: toolchain.name.into(),
-                path: toolchain.path.into(),
+            response.toolchain.and_then(|toolchain| {
+                Some(Toolchain {
+                    language_name: language_name.clone(),
+                    name: toolchain.name.into(),
+                    path: toolchain.path.into(),
+                    as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
+                })
             })
         })
     }

crates/proto/proto/zed.proto 🔗

@@ -2473,6 +2473,7 @@ message ListToolchains {
 message Toolchain {
     string name = 1;
     string path = 2;
+    string raw_json = 3;
 }
 
 message ToolchainGroup {

crates/workspace/src/persistence.rs 🔗

@@ -1,6 +1,6 @@
 pub mod model;
 
-use std::path::Path;
+use std::{path::Path, str::FromStr};
 
 use anyhow::{anyhow, bail, Context, Result};
 use client::DevServerProjectId;
@@ -380,6 +380,9 @@ define_connection! {
             PRIMARY KEY (workspace_id, worktree_id, language_name)
         );
     ),
+    sql!(
+        ALTER TABLE toolchains ADD COLUMN raw_json TEXT DEFAULT "{}";
+    ),
     ];
 }
 
@@ -1080,18 +1083,19 @@ impl WorkspaceDb {
         self.write(move |this| {
             let mut select = this
                 .select_bound(sql!(
-                    SELECT name, path FROM toolchains WHERE workspace_id = ? AND language_name = ? AND worktree_id = ?
+                    SELECT name, path, raw_json FROM toolchains WHERE workspace_id = ? AND language_name = ? AND worktree_id = ?
                 ))
                 .context("Preparing insertion")?;
 
-            let toolchain: Vec<(String, String)> =
+            let toolchain: Vec<(String, String, String)> =
                 select((workspace_id, language_name.0.to_owned(), worktree_id.to_usize()))?;
 
-            Ok(toolchain.into_iter().next().map(|(name, path)| Toolchain {
+            Ok(toolchain.into_iter().next().and_then(|(name, path, raw_json)| Some(Toolchain {
                 name: name.into(),
                 path: path.into(),
                 language_name,
-            }))
+                as_json: serde_json::Value::from_str(&raw_json).ok()?
+            })))
         })
         .await
     }
@@ -1103,18 +1107,19 @@ impl WorkspaceDb {
         self.write(move |this| {
             let mut select = this
                 .select_bound(sql!(
-                    SELECT name, path, worktree_id, language_name FROM toolchains WHERE workspace_id = ?
+                    SELECT name, path, worktree_id, language_name, raw_json FROM toolchains WHERE workspace_id = ?
                 ))
                 .context("Preparing insertion")?;
 
-            let toolchain: Vec<(String, String, u64, String)> =
+            let toolchain: Vec<(String, String, u64, String, String)> =
                 select(workspace_id)?;
 
-            Ok(toolchain.into_iter().map(|(name, path, worktree_id, language_name)| (Toolchain {
+            Ok(toolchain.into_iter().filter_map(|(name, path, worktree_id, language_name, raw_json)| Some((Toolchain {
                 name: name.into(),
                 path: path.into(),
                 language_name: LanguageName::new(&language_name),
-            }, WorktreeId::from_proto(worktree_id))).collect())
+                as_json: serde_json::Value::from_str(&raw_json).ok()?
+            }, WorktreeId::from_proto(worktree_id)))).collect())
         })
         .await
     }