Add language toolchains (#19576)

This PR adds support for selecting toolchains for a given language (e.g.
Rust toolchains or Python virtual environments) with support for SSH
projects provided out of the box. For Python we piggy-back off of
[PET](https://github.com/microsoft/python-environment-tools), a library
maintained by Microsoft.
Closes #16421
Closes #7646

Release Notes:

- Added toolchain selector to the status bar (with initial support for
Python virtual environments)
This commit is contained in:
Piotr Osiewicz 2024-10-28 15:34:03 +01:00 committed by GitHub
parent 03bd95405b
commit cdddb4d360
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 2221 additions and 133 deletions

View file

@ -7,6 +7,8 @@ use client::DevServerProjectId;
use db::{define_connection, query, sqlez::connection::Connection, sqlez_macros::sql};
use gpui::{point, size, Axis, Bounds, WindowBounds, WindowId};
use language::{LanguageName, Toolchain};
use project::WorktreeId;
use remote::ssh_session::SshProjectId;
use sqlez::{
bindable::{Bind, Column, StaticColumnCount},
@ -204,7 +206,8 @@ define_connection! {
// preview: bool // Indicates if this item is a preview item
// )
pub static ref DB: WorkspaceDb<()> =
&[sql!(
&[
sql!(
CREATE TABLE workspaces(
workspace_id INTEGER PRIMARY KEY,
workspace_location BLOB UNIQUE,
@ -367,6 +370,16 @@ define_connection! {
sql!(
ALTER TABLE ssh_projects RENAME COLUMN path TO paths;
),
sql!(
CREATE TABLE toolchains (
workspace_id INTEGER,
worktree_id INTEGER,
language_name TEXT NOT NULL,
name TEXT NOT NULL,
path TEXT NOT NULL,
PRIMARY KEY (workspace_id, worktree_id, language_name)
);
),
];
}
@ -528,6 +541,7 @@ impl WorkspaceDb {
match workspace.location {
SerializedWorkspaceLocation::Local(local_paths, local_paths_order) => {
conn.exec_bound(sql!(
DELETE FROM toolchains WHERE workspace_id = ?1;
DELETE FROM workspaces WHERE local_paths = ? AND workspace_id != ?
))?((&local_paths, workspace.id))
.context("clearing out old locations")?;
@ -576,6 +590,7 @@ impl WorkspaceDb {
}
SerializedWorkspaceLocation::Ssh(ssh_project) => {
conn.exec_bound(sql!(
DELETE FROM toolchains WHERE workspace_id = ?1;
DELETE FROM workspaces WHERE ssh_project_id = ? AND workspace_id != ?
))?((ssh_project.id.0, workspace.id))
.context("clearing out old locations")?;
@ -737,6 +752,7 @@ impl WorkspaceDb {
query! {
pub async fn delete_workspace_by_id(id: WorkspaceId) -> Result<()> {
DELETE FROM toolchains WHERE workspace_id = ?1;
DELETE FROM workspaces
WHERE workspace_id IS ?
}
@ -751,6 +767,7 @@ impl WorkspaceDb {
DELETE FROM dev_server_projects WHERE id = ?
))?(id.0)?;
conn.exec_bound(sql!(
DELETE FROM toolchains WHERE workspace_id = ?1;
DELETE FROM workspaces
WHERE dev_server_project_id IS ?
))?(id.0)
@ -1053,6 +1070,83 @@ impl WorkspaceDb {
WHERE workspace_id = ?1
}
}
pub async fn toolchain(
&self,
workspace_id: WorkspaceId,
worktree_id: WorktreeId,
language_name: LanguageName,
) -> Result<Option<Toolchain>> {
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 = ?
))
.context("Preparing insertion")?;
let toolchain: Vec<(String, String)> =
select((workspace_id, language_name.0.to_owned(), worktree_id.to_usize()))?;
Ok(toolchain.into_iter().next().map(|(name, path)| Toolchain {
name: name.into(),
path: path.into(),
language_name,
}))
})
.await
}
pub(crate) async fn toolchains(
&self,
workspace_id: WorkspaceId,
) -> Result<Vec<(Toolchain, WorktreeId)>> {
self.write(move |this| {
let mut select = this
.select_bound(sql!(
SELECT name, path, worktree_id, language_name FROM toolchains WHERE workspace_id = ?
))
.context("Preparing insertion")?;
let toolchain: Vec<(String, String, u64, String)> =
select(workspace_id)?;
Ok(toolchain.into_iter().map(|(name, path, worktree_id, language_name)| (Toolchain {
name: name.into(),
path: path.into(),
language_name: LanguageName::new(&language_name),
}, WorktreeId::from_proto(worktree_id))).collect())
})
.await
}
pub async fn set_toolchain(
&self,
workspace_id: WorkspaceId,
worktree_id: WorktreeId,
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 (?, ?, ?, ?, ?)
ON CONFLICT DO
UPDATE SET
name = ?4,
path = ?5
))
.context("Preparing insertion")?;
insert((
workspace_id,
worktree_id.to_usize(),
toolchain.language_name.0.as_ref(),
toolchain.name.as_ref(),
toolchain.path.as_ref(),
))?;
Ok(())
}).await
}
}
#[cfg(test)]