toolchains: Allow users to provide custom paths to toolchains (#37009)
- **toolchains: Add new state to toolchain selector** - **Use toolchain term for Add Toolchain button** - **Hoist out a meta function for toolchain listers** Closes #27332 Release Notes: - python: Users can now specify a custom path to their virtual environment from within the picker. --------- Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
co-authored by
Danilo Leal
parent
59bdbf5a5d
commit
6a7b84eb87
@@ -9,7 +9,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result, bail};
|
||||
use collections::HashMap;
|
||||
use collections::{HashMap, IndexSet};
|
||||
use db::{
|
||||
query,
|
||||
sqlez::{connection::Connection, domain::Domain},
|
||||
@@ -18,16 +18,16 @@ use db::{
|
||||
use gpui::{Axis, Bounds, Task, WindowBounds, WindowId, point, size};
|
||||
use project::debugger::breakpoint_store::{BreakpointState, SourceBreakpoint};
|
||||
|
||||
use language::{LanguageName, Toolchain};
|
||||
use language::{LanguageName, Toolchain, ToolchainScope};
|
||||
use project::WorktreeId;
|
||||
use remote::{RemoteConnectionOptions, SshConnectionOptions, WslConnectionOptions};
|
||||
use sqlez::{
|
||||
bindable::{Bind, Column, StaticColumnCount},
|
||||
statement::{SqlType, Statement},
|
||||
statement::Statement,
|
||||
thread_safe_connection::ThreadSafeConnection,
|
||||
};
|
||||
|
||||
use ui::{App, px};
|
||||
use ui::{App, SharedString, px};
|
||||
use util::{ResultExt, maybe};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -169,6 +169,7 @@ impl From<BreakpointState> for BreakpointStateWrapper<'static> {
|
||||
BreakpointStateWrapper(Cow::Owned(kind))
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticColumnCount for BreakpointStateWrapper<'_> {
|
||||
fn column_count() -> usize {
|
||||
1
|
||||
@@ -193,11 +194,6 @@ impl Column for BreakpointStateWrapper<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is used to implement traits on Vec<breakpoint>
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Breakpoints(Vec<Breakpoint>);
|
||||
|
||||
impl sqlez::bindable::StaticColumnCount for Breakpoint {
|
||||
fn column_count() -> usize {
|
||||
// Position, log message, condition message, and hit condition message
|
||||
@@ -246,26 +242,6 @@ impl Column for Breakpoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl Column for Breakpoints {
|
||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||
let mut breakpoints = Vec::new();
|
||||
let mut index = start_index;
|
||||
|
||||
loop {
|
||||
match statement.column_type(index) {
|
||||
Ok(SqlType::Null) => break,
|
||||
_ => {
|
||||
let (breakpoint, next_index) = Breakpoint::column(statement, index)?;
|
||||
|
||||
breakpoints.push(breakpoint);
|
||||
index = next_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((Breakpoints(breakpoints), index))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct SerializedPixels(gpui::Pixels);
|
||||
impl sqlez::bindable::StaticColumnCount for SerializedPixels {}
|
||||
@@ -711,6 +687,18 @@ impl Domain for WorkspaceDb {
|
||||
|
||||
CREATE UNIQUE INDEX ix_workspaces_location ON workspaces(remote_connection_id, paths);
|
||||
),
|
||||
sql!(CREATE TABLE user_toolchains (
|
||||
remote_connection_id INTEGER,
|
||||
workspace_id INTEGER NOT NULL,
|
||||
worktree_id INTEGER NOT NULL,
|
||||
relative_worktree_path TEXT NOT NULL,
|
||||
language_name TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
raw_json TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (workspace_id, worktree_id, relative_worktree_path, language_name, name, path, raw_json)
|
||||
) STRICT;),
|
||||
];
|
||||
|
||||
// Allow recovering from bad migration that was initially shipped to nightly
|
||||
@@ -831,6 +819,7 @@ impl WorkspaceDb {
|
||||
session_id: None,
|
||||
breakpoints: self.breakpoints(workspace_id),
|
||||
window_id,
|
||||
user_toolchains: self.user_toolchains(workspace_id, remote_connection_id),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -880,6 +869,73 @@ impl WorkspaceDb {
|
||||
}
|
||||
}
|
||||
|
||||
fn user_toolchains(
|
||||
&self,
|
||||
workspace_id: WorkspaceId,
|
||||
remote_connection_id: Option<RemoteConnectionId>,
|
||||
) -> BTreeMap<ToolchainScope, IndexSet<Toolchain>> {
|
||||
type RowKind = (WorkspaceId, u64, String, String, String, String, String);
|
||||
|
||||
let toolchains: Vec<RowKind> = self
|
||||
.select_bound(sql! {
|
||||
SELECT workspace_id, worktree_id, relative_worktree_path,
|
||||
language_name, name, path, raw_json
|
||||
FROM user_toolchains WHERE remote_connection_id IS ?1 AND (
|
||||
workspace_id IN (0, ?2)
|
||||
)
|
||||
})
|
||||
.and_then(|mut statement| {
|
||||
(statement)((remote_connection_id.map(|id| id.0), workspace_id))
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut ret = BTreeMap::<_, IndexSet<_>>::default();
|
||||
|
||||
for (
|
||||
_workspace_id,
|
||||
worktree_id,
|
||||
relative_worktree_path,
|
||||
language_name,
|
||||
name,
|
||||
path,
|
||||
raw_json,
|
||||
) in toolchains
|
||||
{
|
||||
// INTEGER's that are primary keys (like workspace ids, remote connection ids and such) start at 1, so we're safe to
|
||||
let scope = if _workspace_id == WorkspaceId(0) {
|
||||
debug_assert_eq!(worktree_id, u64::MAX);
|
||||
debug_assert_eq!(relative_worktree_path, String::default());
|
||||
ToolchainScope::Global
|
||||
} else {
|
||||
debug_assert_eq!(workspace_id, _workspace_id);
|
||||
debug_assert_eq!(
|
||||
worktree_id == u64::MAX,
|
||||
relative_worktree_path == String::default()
|
||||
);
|
||||
|
||||
if worktree_id != u64::MAX && relative_worktree_path != String::default() {
|
||||
ToolchainScope::Subproject(
|
||||
WorktreeId::from_usize(worktree_id as usize),
|
||||
Arc::from(relative_worktree_path.as_ref()),
|
||||
)
|
||||
} else {
|
||||
ToolchainScope::Project
|
||||
}
|
||||
};
|
||||
let Ok(as_json) = serde_json::from_str(&raw_json) else {
|
||||
continue;
|
||||
};
|
||||
let toolchain = Toolchain {
|
||||
name: SharedString::from(name),
|
||||
path: SharedString::from(path),
|
||||
language_name: LanguageName::from_proto(language_name),
|
||||
as_json,
|
||||
};
|
||||
ret.entry(scope).or_default().insert(toolchain);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
/// Saves a workspace using the worktree roots. Will garbage collect any workspaces
|
||||
/// that used this workspace previously
|
||||
pub(crate) async fn save_workspace(&self, workspace: SerializedWorkspace) {
|
||||
@@ -935,6 +991,22 @@ impl WorkspaceDb {
|
||||
}
|
||||
}
|
||||
}
|
||||
for (scope, toolchains) in workspace.user_toolchains {
|
||||
for toolchain in toolchains {
|
||||
let query = sql!(INSERT OR REPLACE INTO user_toolchains(remote_connection_id, workspace_id, worktree_id, relative_worktree_path, language_name, name, path, raw_json) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8));
|
||||
let (workspace_id, worktree_id, relative_worktree_path) = match scope {
|
||||
ToolchainScope::Subproject(worktree_id, ref path) => (Some(workspace.id), Some(worktree_id), Some(path.to_string_lossy().into_owned())),
|
||||
ToolchainScope::Project => (Some(workspace.id), None, None),
|
||||
ToolchainScope::Global => (None, None, None),
|
||||
};
|
||||
let args = (remote_connection_id, workspace_id.unwrap_or(WorkspaceId(0)), worktree_id.map_or(usize::MAX,|id| id.to_usize()), relative_worktree_path.unwrap_or_default(),
|
||||
toolchain.language_name.as_ref().to_owned(), toolchain.name.to_string(), toolchain.path.to_string(), toolchain.as_json.to_string());
|
||||
if let Err(err) = conn.exec_bound(query)?(args) {
|
||||
log::error!("{err}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
conn.exec_bound(sql!(
|
||||
DELETE
|
||||
@@ -1797,6 +1869,7 @@ mod tests {
|
||||
},
|
||||
session_id: None,
|
||||
window_id: None,
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
db.save_workspace(workspace.clone()).await;
|
||||
@@ -1917,6 +1990,7 @@ mod tests {
|
||||
},
|
||||
session_id: None,
|
||||
window_id: None,
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
db.save_workspace(workspace.clone()).await;
|
||||
@@ -1950,6 +2024,7 @@ mod tests {
|
||||
breakpoints: collections::BTreeMap::default(),
|
||||
session_id: None,
|
||||
window_id: None,
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
db.save_workspace(workspace_without_breakpoint.clone())
|
||||
@@ -2047,6 +2122,7 @@ mod tests {
|
||||
breakpoints: Default::default(),
|
||||
session_id: None,
|
||||
window_id: None,
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
let workspace_2 = SerializedWorkspace {
|
||||
@@ -2061,6 +2137,7 @@ mod tests {
|
||||
breakpoints: Default::default(),
|
||||
session_id: None,
|
||||
window_id: None,
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
db.save_workspace(workspace_1.clone()).await;
|
||||
@@ -2167,6 +2244,7 @@ mod tests {
|
||||
centered_layout: false,
|
||||
session_id: None,
|
||||
window_id: Some(999),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
db.save_workspace(workspace.clone()).await;
|
||||
@@ -2200,6 +2278,7 @@ mod tests {
|
||||
centered_layout: false,
|
||||
session_id: None,
|
||||
window_id: Some(1),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
let mut workspace_2 = SerializedWorkspace {
|
||||
@@ -2214,6 +2293,7 @@ mod tests {
|
||||
breakpoints: Default::default(),
|
||||
session_id: None,
|
||||
window_id: Some(2),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
db.save_workspace(workspace_1.clone()).await;
|
||||
@@ -2255,6 +2335,7 @@ mod tests {
|
||||
centered_layout: false,
|
||||
session_id: None,
|
||||
window_id: Some(3),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
db.save_workspace(workspace_3.clone()).await;
|
||||
@@ -2292,6 +2373,7 @@ mod tests {
|
||||
breakpoints: Default::default(),
|
||||
session_id: Some("session-id-1".to_owned()),
|
||||
window_id: Some(10),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
let workspace_2 = SerializedWorkspace {
|
||||
@@ -2306,6 +2388,7 @@ mod tests {
|
||||
breakpoints: Default::default(),
|
||||
session_id: Some("session-id-1".to_owned()),
|
||||
window_id: Some(20),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
let workspace_3 = SerializedWorkspace {
|
||||
@@ -2320,6 +2403,7 @@ mod tests {
|
||||
breakpoints: Default::default(),
|
||||
session_id: Some("session-id-2".to_owned()),
|
||||
window_id: Some(30),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
let workspace_4 = SerializedWorkspace {
|
||||
@@ -2334,6 +2418,7 @@ mod tests {
|
||||
breakpoints: Default::default(),
|
||||
session_id: None,
|
||||
window_id: None,
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
let connection_id = db
|
||||
@@ -2359,6 +2444,7 @@ mod tests {
|
||||
breakpoints: Default::default(),
|
||||
session_id: Some("session-id-2".to_owned()),
|
||||
window_id: Some(50),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
let workspace_6 = SerializedWorkspace {
|
||||
@@ -2373,6 +2459,7 @@ mod tests {
|
||||
centered_layout: false,
|
||||
session_id: Some("session-id-3".to_owned()),
|
||||
window_id: Some(60),
|
||||
user_toolchains: Default::default(),
|
||||
};
|
||||
|
||||
db.save_workspace(workspace_1.clone()).await;
|
||||
@@ -2424,6 +2511,7 @@ mod tests {
|
||||
centered_layout: false,
|
||||
session_id: None,
|
||||
window_id: None,
|
||||
user_toolchains: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2458,6 +2546,7 @@ mod tests {
|
||||
session_id: Some("one-session".to_owned()),
|
||||
breakpoints: Default::default(),
|
||||
window_id: Some(window_id),
|
||||
user_toolchains: Default::default(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -2555,6 +2644,7 @@ mod tests {
|
||||
session_id: Some("one-session".to_owned()),
|
||||
breakpoints: Default::default(),
|
||||
window_id: Some(window_id),
|
||||
user_toolchains: Default::default(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
@@ -5,12 +5,14 @@ use crate::{
|
||||
};
|
||||
use anyhow::Result;
|
||||
use async_recursion::async_recursion;
|
||||
use collections::IndexSet;
|
||||
use db::sqlez::{
|
||||
bindable::{Bind, Column, StaticColumnCount},
|
||||
statement::Statement,
|
||||
};
|
||||
use gpui::{AsyncWindowContext, Entity, WeakEntity};
|
||||
|
||||
use language::{Toolchain, ToolchainScope};
|
||||
use project::{Project, debugger::breakpoint_store::SourceBreakpoint};
|
||||
use remote::RemoteConnectionOptions;
|
||||
use std::{
|
||||
@@ -57,6 +59,7 @@ pub(crate) struct SerializedWorkspace {
|
||||
pub(crate) docks: DockStructure,
|
||||
pub(crate) session_id: Option<String>,
|
||||
pub(crate) breakpoints: BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
|
||||
pub(crate) user_toolchains: BTreeMap<ToolchainScope, IndexSet<Toolchain>>,
|
||||
pub(crate) window_id: Option<u64>,
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ use postage::stream::Stream;
|
||||
use project::{
|
||||
DirectoryLister, Project, ProjectEntryId, ProjectPath, ResolvedPath, Worktree, WorktreeId,
|
||||
debugger::{breakpoint_store::BreakpointStoreEvent, session::ThreadStatus},
|
||||
toolchain_store::ToolchainStoreEvent,
|
||||
};
|
||||
use remote::{RemoteClientDelegate, RemoteConnectionOptions, remote_client::ConnectionIdentifier};
|
||||
use schemars::JsonSchema;
|
||||
@@ -1275,6 +1276,19 @@ impl Workspace {
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
if let Some(toolchain_store) = project.read(cx).toolchain_store() {
|
||||
cx.subscribe_in(
|
||||
&toolchain_store,
|
||||
window,
|
||||
|workspace, _, event, window, cx| match event {
|
||||
ToolchainStoreEvent::CustomToolchainsModified => {
|
||||
workspace.serialize_workspace(window, cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
cx.on_focus_lost(window, |this, window, cx| {
|
||||
let focus_handle = this.focus_handle(cx);
|
||||
@@ -1565,6 +1579,16 @@ impl Workspace {
|
||||
})?
|
||||
.await;
|
||||
}
|
||||
if let Some(workspace) = serialized_workspace.as_ref() {
|
||||
project_handle.update(cx, |this, cx| {
|
||||
for (scope, toolchains) in &workspace.user_toolchains {
|
||||
for toolchain in toolchains {
|
||||
this.add_toolchain(toolchain.clone(), scope.clone(), cx);
|
||||
}
|
||||
}
|
||||
})?;
|
||||
}
|
||||
|
||||
let window = if let Some(window) = requesting_window {
|
||||
let centered_layout = serialized_workspace
|
||||
.as_ref()
|
||||
@@ -5240,10 +5264,16 @@ impl Workspace {
|
||||
.read(cx)
|
||||
.all_source_breakpoints(cx)
|
||||
});
|
||||
let user_toolchains = self
|
||||
.project
|
||||
.read(cx)
|
||||
.user_toolchains(cx)
|
||||
.unwrap_or_default();
|
||||
|
||||
let center_group = build_serialized_pane_group(&self.center.root, window, cx);
|
||||
let docks = build_serialized_docks(self, window, cx);
|
||||
let window_bounds = Some(SerializedWindowBounds(window.window_bounds()));
|
||||
|
||||
let serialized_workspace = SerializedWorkspace {
|
||||
id: database_id,
|
||||
location,
|
||||
@@ -5256,6 +5286,7 @@ impl Workspace {
|
||||
session_id: self.session_id.clone(),
|
||||
breakpoints,
|
||||
window_id: Some(window.window_handle().window_id().as_u64()),
|
||||
user_toolchains,
|
||||
};
|
||||
|
||||
window.spawn(cx, async move |_| {
|
||||
|
||||
Reference in New Issue
Block a user