toolchains: Add support for relative paths (#27777)

Piotr Osiewicz created

Closes #ISSUE

Release Notes:

- N/A

Change summary

crates/dap_adapters/src/python.rs                   |   1 
crates/language/src/toolchain.rs                    |   6 
crates/languages/src/python.rs                      |  17 +
crates/project/src/project.rs                       |  14 
crates/project/src/terminals.rs                     |   9 
crates/project/src/toolchain_store.rs               | 122 ++++++++------
crates/proto/proto/zed.proto                        |   3 
crates/repl/src/kernels/mod.rs                      |  15 +
crates/toolchain_selector/src/active_toolchain.rs   |  37 +++-
crates/toolchain_selector/src/toolchain_selector.rs |  22 ++
crates/workspace/src/persistence.rs                 |  17 +
crates/workspace/src/workspace.rs                   |   8 
12 files changed, 178 insertions(+), 93 deletions(-)

Detailed changes

crates/dap_adapters/src/python.rs 🔗

@@ -89,6 +89,7 @@ impl DebugAdapter for PythonDebugAdapter {
             .toolchain_store()
             .active_toolchain(
                 delegate.worktree_id(),
+                Arc::from("".as_ref()),
                 language::LanguageName::new(Self::LANGUAGE_NAME),
                 cx,
             )

crates/language/src/toolchain.rs 🔗

@@ -4,7 +4,10 @@
 //! which is a set of tools used to interact with the projects written in said language.
 //! For example, a Python project can have an associated virtual environment; a Rust project can have a toolchain override.
 
-use std::{path::PathBuf, sync::Arc};
+use std::{
+    path::{Path, PathBuf},
+    sync::Arc,
+};
 
 use async_trait::async_trait;
 use collections::HashMap;
@@ -52,6 +55,7 @@ pub trait LanguageToolchainStore {
     async fn active_toolchain(
         self: Arc<Self>,
         worktree_id: WorktreeId,
+        relative_path: Arc<Path>,
         language_name: LanguageName,
         cx: &mut AsyncApp,
     ) -> Option<Toolchain>;

crates/languages/src/python.rs 🔗

@@ -293,7 +293,12 @@ impl LspAdapter for PythonLspAdapter {
         cx: &mut AsyncApp,
     ) -> Result<Value> {
         let toolchain = toolchains
-            .active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
+            .active_toolchain(
+                adapter.worktree_id(),
+                Arc::from("".as_ref()),
+                LanguageName::new("Python"),
+                cx,
+            )
             .await;
         cx.update(move |cx| {
             let mut user_settings =
@@ -373,7 +378,7 @@ impl ContextProvider for PythonContextProvider {
         cx.spawn(async move |cx| {
             let active_toolchain = if let Some(worktree_id) = worktree_id {
                 toolchains
-                    .active_toolchain(worktree_id, "Python".into(), cx)
+                    .active_toolchain(worktree_id, Arc::from("".as_ref()), "Python".into(), cx)
                     .await
                     .map_or_else(
                         || "python3".to_owned(),
@@ -900,6 +905,7 @@ impl LspAdapter for PyLspAdapter {
             let venv = toolchains
                 .active_toolchain(
                     delegate.worktree_id(),
+                    Arc::from("".as_ref()),
                     LanguageName::new("Python"),
                     &mut cx.clone(),
                 )
@@ -1046,7 +1052,12 @@ impl LspAdapter for PyLspAdapter {
         cx: &mut AsyncApp,
     ) -> Result<Value> {
         let toolchain = toolchains
-            .active_toolchain(adapter.worktree_id(), LanguageName::new("Python"), cx)
+            .active_toolchain(
+                adapter.worktree_id(),
+                Arc::from("".as_ref()),
+                LanguageName::new("Python"),
+                cx,
+            )
             .await;
         cx.update(move |cx| {
             let mut user_settings =

crates/project/src/project.rs 🔗

@@ -3065,7 +3065,7 @@ impl Project {
 
     pub fn available_toolchains(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         language_name: LanguageName,
         cx: &App,
     ) -> Task<Option<ToolchainList>> {
@@ -3074,7 +3074,7 @@ impl Project {
                 cx.update(|cx| {
                     toolchain_store
                         .read(cx)
-                        .list_toolchains(worktree_id, language_name, cx)
+                        .list_toolchains(path, language_name, cx)
                 })
                 .ok()?
                 .await
@@ -3098,20 +3098,18 @@ impl Project {
 
     pub fn activate_toolchain(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         toolchain: Toolchain,
         cx: &mut App,
     ) -> Task<Option<()>> {
         let Some(toolchain_store) = self.toolchain_store.clone() else {
             return Task::ready(None);
         };
-        toolchain_store.update(cx, |this, cx| {
-            this.activate_toolchain(worktree_id, toolchain, cx)
-        })
+        toolchain_store.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
     }
     pub fn active_toolchain(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         language_name: LanguageName,
         cx: &App,
     ) -> Task<Option<Toolchain>> {
@@ -3120,7 +3118,7 @@ impl Project {
         };
         toolchain_store
             .read(cx)
-            .active_toolchain(worktree_id, language_name, cx)
+            .active_toolchain(path, language_name, cx)
     }
     pub fn language_server_statuses<'a>(
         &'a self,

crates/project/src/terminals.rs 🔗

@@ -1,4 +1,4 @@
-use crate::Project;
+use crate::{Project, ProjectPath};
 use anyhow::{Context as _, Result};
 use collections::HashMap;
 use gpui::{AnyWindowHandle, App, AppContext as _, Context, Entity, Task, WeakEntity};
@@ -407,14 +407,17 @@ impl Project {
         cx: &Context<Project>,
     ) -> Task<Option<PathBuf>> {
         cx.spawn(async move |this, cx| {
-            if let Some((worktree, _)) = this
+            if let Some((worktree, relative_path)) = this
                 .update(cx, |this, cx| this.find_worktree(&abs_path, cx))
                 .ok()?
             {
                 let toolchain = this
                     .update(cx, |this, cx| {
                         this.active_toolchain(
-                            worktree.read(cx).id(),
+                            ProjectPath {
+                                worktree_id: worktree.read(cx).id(),
+                                path: relative_path.into(),
+                            },
                             LanguageName::new("Python"),
                             cx,
                         )

crates/project/src/toolchain_store.rs 🔗

@@ -1,4 +1,8 @@
-use std::{path::PathBuf, str::FromStr, sync::Arc};
+use std::{
+    path::{Path, PathBuf},
+    str::FromStr,
+    sync::Arc,
+};
 
 use anyhow::{bail, Result};
 
@@ -15,7 +19,7 @@ use rpc::{
 use settings::WorktreeId;
 use util::ResultExt as _;
 
-use crate::{worktree_store::WorktreeStore, ProjectEnvironment};
+use crate::{worktree_store::WorktreeStore, ProjectEnvironment, ProjectPath};
 
 pub struct ToolchainStore(ToolchainStoreInner);
 enum ToolchainStoreInner {
@@ -58,56 +62,46 @@ impl ToolchainStore {
     }
     pub(crate) fn activate_toolchain(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         toolchain: Toolchain,
         cx: &mut App,
     ) -> Task<Option<()>> {
         match &self.0 {
-            ToolchainStoreInner::Local(local, _) => local.update(cx, |this, cx| {
-                this.activate_toolchain(worktree_id, toolchain, cx)
-            }),
+            ToolchainStoreInner::Local(local, _) => {
+                local.update(cx, |this, cx| this.activate_toolchain(path, toolchain, cx))
+            }
             ToolchainStoreInner::Remote(remote) => {
-                remote
-                    .read(cx)
-                    .activate_toolchain(worktree_id, toolchain, cx)
+                remote.read(cx).activate_toolchain(path, toolchain, cx)
             }
         }
     }
     pub(crate) fn list_toolchains(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         language_name: LanguageName,
         cx: &App,
     ) -> Task<Option<ToolchainList>> {
         match &self.0 {
             ToolchainStoreInner::Local(local, _) => {
-                local
-                    .read(cx)
-                    .list_toolchains(worktree_id, language_name, cx)
+                local.read(cx).list_toolchains(path, language_name, cx)
             }
             ToolchainStoreInner::Remote(remote) => {
-                remote
-                    .read(cx)
-                    .list_toolchains(worktree_id, language_name, cx)
+                remote.read(cx).list_toolchains(path, language_name, cx)
             }
         }
     }
     pub(crate) fn active_toolchain(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         language_name: LanguageName,
         cx: &App,
     ) -> Task<Option<Toolchain>> {
         match &self.0 {
             ToolchainStoreInner::Local(local, _) => {
-                local
-                    .read(cx)
-                    .active_toolchain(worktree_id, language_name, cx)
+                local.read(cx).active_toolchain(path, language_name, cx)
             }
             ToolchainStoreInner::Remote(remote) => {
-                remote
-                    .read(cx)
-                    .active_toolchain(worktree_id, language_name, cx)
+                remote.read(cx).active_toolchain(path, language_name, cx)
             }
         }
     }
@@ -130,7 +124,12 @@ impl ToolchainStore {
                 language_name,
             };
             let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-            Ok(this.activate_toolchain(worktree_id, toolchain, cx))
+            let path: Arc<Path> = if let Some(path) = envelope.payload.path {
+                Arc::from(path.as_ref())
+            } else {
+                Arc::from("".as_ref())
+            };
+            Ok(this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx))
         })??
         .await;
         Ok(proto::Ack {})
@@ -144,7 +143,14 @@ impl ToolchainStore {
             .update(&mut cx, |this, cx| {
                 let language_name = LanguageName::from_proto(envelope.payload.language_name);
                 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-                this.active_toolchain(worktree_id, language_name, cx)
+                this.active_toolchain(
+                    ProjectPath {
+                        worktree_id,
+                        path: Arc::from(envelope.payload.path.as_deref().unwrap_or("").as_ref()),
+                    },
+                    language_name,
+                    cx,
+                )
             })?
             .await;
 
@@ -169,7 +175,8 @@ impl ToolchainStore {
             .update(&mut cx, |this, cx| {
                 let language_name = LanguageName::from_proto(envelope.payload.language_name);
                 let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
-                this.list_toolchains(worktree_id, language_name, cx)
+                let path = Arc::from(envelope.payload.path.as_deref().unwrap_or("").as_ref());
+                this.list_toolchains(ProjectPath { worktree_id, path }, language_name, cx)
             })?
             .await;
         let has_values = toolchains.is_some();
@@ -222,7 +229,7 @@ struct LocalToolchainStore {
     languages: Arc<LanguageRegistry>,
     worktree_store: Entity<WorktreeStore>,
     project_environment: Entity<ProjectEnvironment>,
-    active_toolchains: BTreeMap<(WorktreeId, LanguageName), Toolchain>,
+    active_toolchains: BTreeMap<(WorktreeId, LanguageName), BTreeMap<Arc<Path>, Toolchain>>,
 }
 
 #[async_trait(?Send)]
@@ -230,12 +237,13 @@ impl language::LanguageToolchainStore for LocalStore {
     async fn active_toolchain(
         self: Arc<Self>,
         worktree_id: WorktreeId,
+        path: Arc<Path>,
         language_name: LanguageName,
         cx: &mut AsyncApp,
     ) -> Option<Toolchain> {
         self.0
             .update(cx, |this, cx| {
-                this.active_toolchain(worktree_id, language_name, cx)
+                this.active_toolchain(ProjectPath { worktree_id, path }, language_name, cx)
             })
             .ok()?
             .await
@@ -247,12 +255,13 @@ impl language::LanguageToolchainStore for RemoteStore {
     async fn active_toolchain(
         self: Arc<Self>,
         worktree_id: WorktreeId,
+        path: Arc<Path>,
         language_name: LanguageName,
         cx: &mut AsyncApp,
     ) -> Option<Toolchain> {
         self.0
             .update(cx, |this, cx| {
-                this.active_toolchain(worktree_id, language_name, cx)
+                this.active_toolchain(ProjectPath { worktree_id, path }, language_name, cx)
             })
             .ok()?
             .await
@@ -265,6 +274,7 @@ impl language::LanguageToolchainStore for EmptyToolchainStore {
     async fn active_toolchain(
         self: Arc<Self>,
         _: WorktreeId,
+        _: Arc<Path>,
         _: LanguageName,
         _: &mut AsyncApp,
     ) -> Option<Toolchain> {
@@ -284,16 +294,16 @@ impl EventEmitter<ToolchainStoreEvent> for LocalToolchainStore {}
 impl LocalToolchainStore {
     pub(crate) fn activate_toolchain(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         toolchain: Toolchain,
         cx: &mut Context<Self>,
     ) -> Task<Option<()>> {
         cx.spawn(async move |this, cx| {
             this.update(cx, |this, cx| {
-                this.active_toolchains.insert(
-                    (worktree_id, toolchain.language_name.clone()),
-                    toolchain.clone(),
-                );
+                this.active_toolchains
+                    .entry((path.worktree_id, toolchain.language_name.clone()))
+                    .or_default()
+                    .insert(path.path, toolchain.clone());
                 cx.emit(ToolchainStoreEvent::ToolchainActivated);
             })
             .ok();
@@ -302,7 +312,7 @@ impl LocalToolchainStore {
     }
     pub(crate) fn list_toolchains(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         language_name: LanguageName,
         cx: &App,
     ) -> Task<Option<ToolchainList>> {
@@ -310,17 +320,22 @@ impl LocalToolchainStore {
         let Some(root) = self
             .worktree_store
             .read(cx)
-            .worktree_for_id(worktree_id, cx)
+            .worktree_for_id(path.worktree_id, cx)
             .map(|worktree| worktree.read(cx).abs_path())
         else {
             return Task::ready(None);
         };
 
+        let abs_path = root.join(path.path);
         let environment = self.project_environment.clone();
         cx.spawn(async move |cx| {
             let project_env = environment
                 .update(cx, |environment, cx| {
-                    environment.get_environment(Some(worktree_id), Some(root.clone()), cx)
+                    environment.get_environment(
+                        Some(path.worktree_id),
+                        Some(Arc::from(abs_path.as_path())),
+                        cx,
+                    )
                 })
                 .ok()?
                 .await;
@@ -331,20 +346,26 @@ impl LocalToolchainStore {
                     .await
                     .ok()?;
                 let toolchains = language.toolchain_lister()?;
-                Some(toolchains.list(root.to_path_buf(), project_env).await)
+                Some(toolchains.list(abs_path.to_path_buf(), project_env).await)
             })
             .await
         })
     }
     pub(crate) fn active_toolchain(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         language_name: LanguageName,
         _: &App,
     ) -> Task<Option<Toolchain>> {
+        let ancestors = path.path.ancestors();
         Task::ready(
             self.active_toolchains
-                .get(&(worktree_id, language_name))
+                .get(&(path.worktree_id, language_name))
+                .and_then(|paths| {
+                    ancestors
+                        .into_iter()
+                        .find_map(|root_path| paths.get(root_path))
+                })
                 .cloned(),
         )
     }
@@ -357,24 +378,25 @@ struct RemoteToolchainStore {
 impl RemoteToolchainStore {
     pub(crate) fn activate_toolchain(
         &self,
-        worktree_id: WorktreeId,
+        project_path: ProjectPath,
         toolchain: Toolchain,
         cx: &App,
     ) -> Task<Option<()>> {
         let project_id = self.project_id;
         let client = self.client.clone();
-        cx.spawn(async move |_| {
+        cx.background_spawn(async move {
             let path = PathBuf::from(toolchain.path.to_string());
             let _ = client
                 .request(proto::ActivateToolchain {
                     project_id,
-                    worktree_id: worktree_id.to_proto(),
+                    worktree_id: project_path.worktree_id.to_proto(),
                     language_name: toolchain.language_name.into(),
                     toolchain: Some(proto::Toolchain {
                         name: toolchain.name.into(),
                         path: path.to_proto(),
                         raw_json: toolchain.as_json.to_string(),
                     }),
+                    path: Some(project_path.path.to_string_lossy().into_owned()),
                 })
                 .await
                 .log_err()?;
@@ -384,18 +406,19 @@ impl RemoteToolchainStore {
 
     pub(crate) fn list_toolchains(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         language_name: LanguageName,
         cx: &App,
     ) -> Task<Option<ToolchainList>> {
         let project_id = self.project_id;
         let client = self.client.clone();
-        cx.spawn(async move |_| {
+        cx.background_spawn(async move {
             let response = client
                 .request(proto::ListToolchains {
                     project_id,
-                    worktree_id: worktree_id.to_proto(),
+                    worktree_id: path.worktree_id.to_proto(),
                     language_name: language_name.clone().into(),
+                    path: Some(path.path.to_string_lossy().into_owned()),
                 })
                 .await
                 .log_err()?;
@@ -435,18 +458,19 @@ impl RemoteToolchainStore {
     }
     pub(crate) fn active_toolchain(
         &self,
-        worktree_id: WorktreeId,
+        path: ProjectPath,
         language_name: LanguageName,
         cx: &App,
     ) -> Task<Option<Toolchain>> {
         let project_id = self.project_id;
         let client = self.client.clone();
-        cx.spawn(async move |_| {
+        cx.background_spawn(async move {
             let response = client
                 .request(proto::ActiveToolchain {
                     project_id,
-                    worktree_id: worktree_id.to_proto(),
+                    worktree_id: path.worktree_id.to_proto(),
                     language_name: language_name.clone().into(),
+                    path: Some(path.path.to_string_lossy().into_owned()),
                 })
                 .await
                 .log_err()?;

crates/proto/proto/zed.proto 🔗

@@ -3269,6 +3269,7 @@ message ListToolchains {
     uint64 project_id = 1;
     uint64 worktree_id = 2;
     string language_name = 3;
+    optional string path = 4;
 }
 
 message Toolchain {
@@ -3293,12 +3294,14 @@ message ActivateToolchain {
     uint64 worktree_id = 2;
     Toolchain toolchain = 3;
     string language_name = 4;
+    optional string path = 5;
 }
 
 message ActiveToolchain {
     uint64 project_id = 1;
     uint64 worktree_id = 2;
     string language_name = 3;
+    optional string path = 4;
 }
 
 message ActiveToolchainResponse {

crates/repl/src/kernels/mod.rs 🔗

@@ -1,5 +1,5 @@
 mod native_kernel;
-use std::{fmt::Debug, future::Future, path::PathBuf};
+use std::{fmt::Debug, future::Future, path::PathBuf, sync::Arc};
 
 use futures::{
     channel::mpsc::{self, Receiver},
@@ -11,7 +11,7 @@ use language::LanguageName;
 pub use native_kernel::*;
 
 mod remote_kernels;
-use project::{Project, WorktreeId};
+use project::{Project, ProjectPath, WorktreeId};
 pub use remote_kernels::*;
 
 use anyhow::Result;
@@ -81,9 +81,14 @@ pub fn python_env_kernel_specifications(
     cx: &mut App,
 ) -> impl Future<Output = Result<Vec<KernelSpecification>>> {
     let python_language = LanguageName::new("Python");
-    let toolchains = project
-        .read(cx)
-        .available_toolchains(worktree_id, python_language, cx);
+    let toolchains = project.read(cx).available_toolchains(
+        ProjectPath {
+            worktree_id,
+            path: Arc::from("".as_ref()),
+        },
+        python_language,
+        cx,
+    );
     let background_executor = cx.background_executor().clone();
 
     async move {

crates/toolchain_selector/src/active_toolchain.rs 🔗

@@ -1,10 +1,12 @@
+use std::sync::Arc;
+
 use editor::Editor;
 use gpui::{
     div, AsyncWindowContext, Context, Entity, IntoElement, ParentElement, Render, Subscription,
     Task, WeakEntity, Window,
 };
 use language::{Buffer, BufferEvent, LanguageName, Toolchain};
-use project::{Project, WorktreeId};
+use project::{Project, ProjectPath, WorktreeId};
 use ui::{Button, ButtonCommon, Clickable, FluentBuilder, LabelSize, SharedString, Tooltip};
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
@@ -109,9 +111,14 @@ impl ActiveToolchain {
                 .flatten()?;
             let selected_toolchain = workspace
                 .update(cx, |this, cx| {
-                    this.project()
-                        .read(cx)
-                        .active_toolchain(worktree_id, language_name.clone(), cx)
+                    this.project().read(cx).active_toolchain(
+                        ProjectPath {
+                            worktree_id,
+                            path: Arc::from("".as_ref()),
+                        },
+                        language_name.clone(),
+                        cx,
+                    )
                 })
                 .ok()?
                 .await;
@@ -123,21 +130,33 @@ impl ActiveToolchain {
                     .ok()?;
                 let toolchains = cx
                     .update(|_, cx| {
-                        project
-                            .read(cx)
-                            .available_toolchains(worktree_id, language_name, cx)
+                        project.read(cx).available_toolchains(
+                            ProjectPath {
+                                worktree_id,
+                                path: Arc::from("".as_ref()),
+                            },
+                            language_name,
+                            cx,
+                        )
                     })
                     .ok()?
                     .await?;
                 if let Some(toolchain) = toolchains.toolchains.first() {
                     // Since we don't have a selected toolchain, pick one for user here.
                     workspace::WORKSPACE_DB
-                        .set_toolchain(workspace_id, worktree_id, toolchain.clone())
+                        .set_toolchain(workspace_id, worktree_id, "".to_owned(), toolchain.clone())
                         .await
                         .ok()?;
                     project
                         .update(cx, |this, cx| {
-                            this.activate_toolchain(worktree_id, toolchain.clone(), cx)
+                            this.activate_toolchain(
+                                ProjectPath {
+                                    worktree_id,
+                                    path: Arc::from("".as_ref()),
+                                },
+                                toolchain.clone(),
+                                cx,
+                            )
                         })
                         .ok()?
                         .await;

crates/toolchain_selector/src/toolchain_selector.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
 };
 use language::{LanguageName, Toolchain, ToolchainList};
 use picker::{Picker, PickerDelegate};
-use project::{Project, WorktreeId};
+use project::{Project, ProjectPath, WorktreeId};
 use std::{path::Path, sync::Arc};
 use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
 use util::ResultExt;
@@ -169,7 +169,14 @@ impl ToolchainSelectorDelegate {
                 });
                 let available_toolchains = project
                     .update(cx, |this, cx| {
-                        this.available_toolchains(worktree_id, language_name, cx)
+                        this.available_toolchains(
+                            ProjectPath {
+                                worktree_id,
+                                path: Arc::from("".as_ref()),
+                            },
+                            language_name,
+                            cx,
+                        )
                     })
                     .ok()?
                     .await?;
@@ -241,13 +248,20 @@ impl PickerDelegate for ToolchainSelectorDelegate {
                 let worktree_id = self.worktree_id;
                 cx.spawn_in(window, async move |_, cx| {
                     workspace::WORKSPACE_DB
-                        .set_toolchain(workspace_id, worktree_id, toolchain.clone())
+                        .set_toolchain(workspace_id, worktree_id, "".to_owned(), toolchain.clone())
                         .await
                         .log_err();
                     workspace
                         .update(cx, |this, cx| {
                             this.project().update(cx, |this, cx| {
-                                this.activate_toolchain(worktree_id, toolchain, cx)
+                                this.activate_toolchain(
+                                    ProjectPath {
+                                        worktree_id,
+                                        path: Arc::from("".as_ref()),
+                                    },
+                                    toolchain,
+                                    cx,
+                                )
                             })
                         })
                         .ok()?

crates/workspace/src/persistence.rs 🔗

@@ -526,7 +526,8 @@ define_connection! {
     ),
     sql!(
         ALTER TABLE breakpoints DROP COLUMN kind
-    )
+    ),
+    sql!(ALTER TABLE toolchains ADD COLUMN relative_worktree_path TEXT DEFAULT "" NOT NULL)
     ];
 }
 
@@ -1331,23 +1332,23 @@ impl WorkspaceDb {
     pub(crate) async fn toolchains(
         &self,
         workspace_id: WorkspaceId,
-    ) -> Result<Vec<(Toolchain, WorktreeId)>> {
+    ) -> Result<Vec<(Toolchain, WorktreeId, Arc<Path>)>> {
         self.write(move |this| {
             let mut select = this
                 .select_bound(sql!(
-                    SELECT name, path, worktree_id, language_name, raw_json FROM toolchains WHERE workspace_id = ?
+                    SELECT name, path, worktree_id, relative_worktree_path, language_name, raw_json FROM toolchains WHERE workspace_id = ?
                 ))
                 .context("Preparing insertion")?;
 
-            let toolchain: Vec<(String, String, u64, String, String)> =
+            let toolchain: Vec<(String, String, u64, String, String, String)> =
                 select(workspace_id)?;
 
-            Ok(toolchain.into_iter().filter_map(|(name, path, worktree_id, language_name, raw_json)| Some((Toolchain {
+            Ok(toolchain.into_iter().filter_map(|(name, path, worktree_id, relative_worktree_path, language_name, raw_json)| Some((Toolchain {
                 name: name.into(),
                 path: path.into(),
                 language_name: LanguageName::new(&language_name),
                 as_json: serde_json::Value::from_str(&raw_json).ok()?
-            }, WorktreeId::from_proto(worktree_id)))).collect())
+            }, WorktreeId::from_proto(worktree_id), Arc::from(relative_worktree_path.as_ref())))).collect())
         })
         .await
     }
@@ -1355,12 +1356,13 @@ impl WorkspaceDb {
         &self,
         workspace_id: WorkspaceId,
         worktree_id: WorktreeId,
+        relative_worktree_path: String,
         toolchain: Toolchain,
     ) -> Result<()> {
         self.write(move |conn| {
             let mut insert = conn
                 .exec_bound(sql!(
-                    INSERT INTO toolchains(workspace_id, worktree_id, language_name, name, path) VALUES (?, ?, ?, ?,  ?)
+                    INSERT INTO toolchains(workspace_id, worktree_id, relative_worktree_path, language_name, name, path) VALUES (?, ?, ?, ?, ?,  ?)
                     ON CONFLICT DO
                     UPDATE SET
                         name = ?4,
@@ -1372,6 +1374,7 @@ impl WorkspaceDb {
             insert((
                 workspace_id,
                 worktree_id.to_usize(),
+                relative_worktree_path,
                 toolchain.language_name.as_ref(),
                 toolchain.name.as_ref(),
                 toolchain.path.as_ref(),

crates/workspace/src/workspace.rs 🔗

@@ -1273,10 +1273,10 @@ impl Workspace {
             };
 
             let toolchains = DB.toolchains(workspace_id).await?;
-            for (toolchain, worktree_id) in toolchains {
+            for (toolchain, worktree_id, path) in toolchains {
                 project_handle
                     .update(cx, |this, cx| {
-                        this.activate_toolchain(worktree_id, toolchain, cx)
+                        this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
                     })?
                     .await;
             }
@@ -6319,10 +6319,10 @@ pub fn open_ssh_project(
         })?;
 
         let toolchains = DB.toolchains(workspace_id).await?;
-        for (toolchain, worktree_id) in toolchains {
+        for (toolchain, worktree_id, path) in toolchains {
             project
                 .update(cx, |this, cx| {
-                    this.activate_toolchain(worktree_id, toolchain, cx)
+                    this.activate_toolchain(ProjectPath { worktree_id, path }, toolchain, cx)
                 })?
                 .await;
         }