1//! Provides support for language toolchains.
2//!
3//! A language can have associated toolchains,
4//! which is a set of tools used to interact with the projects written in said language.
5//! For example, a Python project can have an associated virtual environment; a Rust project can have a toolchain override.
6
7use std::{
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11
12use async_trait::async_trait;
13use collections::HashMap;
14use fs::Fs;
15use gpui::{AsyncApp, SharedString};
16use settings::WorktreeId;
17
18use crate::{LanguageName, ManifestName};
19
20/// Represents a single toolchain.
21#[derive(Clone, Eq, Debug)]
22pub struct Toolchain {
23 /// User-facing label
24 pub name: SharedString,
25 pub path: SharedString,
26 pub language_name: LanguageName,
27 /// Full toolchain data (including language-specific details)
28 pub as_json: serde_json::Value,
29}
30
31impl std::hash::Hash for Toolchain {
32 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
33 let Self {
34 name,
35 path,
36 language_name,
37 as_json: _,
38 } = self;
39 name.hash(state);
40 path.hash(state);
41 language_name.hash(state);
42 }
43}
44
45impl PartialEq for Toolchain {
46 fn eq(&self, other: &Self) -> bool {
47 let Self {
48 name,
49 path,
50 language_name,
51 as_json: _,
52 } = self;
53 // Do not use as_json for comparisons; it shouldn't impact equality, as it's not user-surfaced.
54 // Thus, there could be multiple entries that look the same in the UI.
55 (name, path, language_name).eq(&(&other.name, &other.path, &other.language_name))
56 }
57}
58
59#[async_trait]
60pub trait ToolchainLister: Send + Sync {
61 async fn list(
62 &self,
63 worktree_root: PathBuf,
64 subroot_relative_path: Arc<Path>,
65 project_env: Option<HashMap<String, String>>,
66 ) -> ToolchainList;
67 // Returns a term which we should use in UI to refer to a toolchain.
68 fn term(&self) -> SharedString;
69 /// Returns the name of the manifest file for this toolchain.
70 fn manifest_name(&self) -> ManifestName;
71 async fn activation_script(&self, toolchain: &Toolchain, fs: &dyn Fs) -> Option<String>;
72}
73
74#[async_trait(?Send)]
75pub trait LanguageToolchainStore: Send + Sync + 'static {
76 async fn active_toolchain(
77 self: Arc<Self>,
78 worktree_id: WorktreeId,
79 relative_path: Arc<Path>,
80 language_name: LanguageName,
81 cx: &mut AsyncApp,
82 ) -> Option<Toolchain>;
83}
84
85pub trait LocalLanguageToolchainStore: Send + Sync + 'static {
86 fn active_toolchain(
87 self: Arc<Self>,
88 worktree_id: WorktreeId,
89 relative_path: &Arc<Path>,
90 language_name: LanguageName,
91 cx: &mut AsyncApp,
92 ) -> Option<Toolchain>;
93}
94
95#[async_trait(?Send)]
96impl<T: LocalLanguageToolchainStore> LanguageToolchainStore for T {
97 async fn active_toolchain(
98 self: Arc<Self>,
99 worktree_id: WorktreeId,
100 relative_path: Arc<Path>,
101 language_name: LanguageName,
102 cx: &mut AsyncApp,
103 ) -> Option<Toolchain> {
104 self.active_toolchain(worktree_id, &relative_path, language_name, cx)
105 }
106}
107
108type DefaultIndex = usize;
109#[derive(Default, Clone, Debug)]
110pub struct ToolchainList {
111 pub toolchains: Vec<Toolchain>,
112 pub default: Option<DefaultIndex>,
113 pub groups: Box<[(usize, SharedString)]>,
114}
115
116impl ToolchainList {
117 pub fn toolchains(&self) -> &[Toolchain] {
118 &self.toolchains
119 }
120 pub fn default_toolchain(&self) -> Option<Toolchain> {
121 self.default.and_then(|ix| self.toolchains.get(ix)).cloned()
122 }
123 pub fn group_for_index(&self, index: usize) -> Option<(usize, SharedString)> {
124 if index >= self.toolchains.len() {
125 return None;
126 }
127 let first_equal_or_greater = self
128 .groups
129 .partition_point(|(group_lower_bound, _)| group_lower_bound <= &index);
130 self.groups
131 .get(first_equal_or_greater.checked_sub(1)?)
132 .cloned()
133 }
134}