toolchain.rs

  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::{path::PathBuf, sync::Arc};
  8
  9use async_trait::async_trait;
 10use collections::HashMap;
 11use fs::Fs;
 12use gpui::{AsyncApp, SharedString};
 13use settings::WorktreeId;
 14use task::ShellKind;
 15use util::rel_path::RelPath;
 16
 17use crate::{LanguageName, ManifestName};
 18
 19/// Represents a single toolchain.
 20#[derive(Clone, Eq, Debug)]
 21pub struct Toolchain {
 22    /// User-facing label
 23    pub name: SharedString,
 24    /// Absolute path
 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
 31/// Declares a scope of a toolchain added by user.
 32///
 33/// When the user adds a toolchain, we give them an option to see that toolchain in:
 34/// - All of their projects
 35/// - A project they're currently in.
 36/// - Only in the subproject they're currently in.
 37#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
 38pub enum ToolchainScope {
 39    Subproject(WorktreeId, Arc<RelPath>),
 40    Project,
 41    /// Available in all projects on this box. It wouldn't make sense to show suggestions across machines.
 42    Global,
 43}
 44
 45impl ToolchainScope {
 46    pub fn label(&self) -> &'static str {
 47        match self {
 48            ToolchainScope::Subproject(_, _) => "Subproject",
 49            ToolchainScope::Project => "Project",
 50            ToolchainScope::Global => "Global",
 51        }
 52    }
 53
 54    pub fn description(&self) -> &'static str {
 55        match self {
 56            ToolchainScope::Subproject(_, _) => {
 57                "Available only in the subproject you're currently in."
 58            }
 59            ToolchainScope::Project => "Available in all locations in your current project.",
 60            ToolchainScope::Global => "Available in all of your projects on this machine.",
 61        }
 62    }
 63}
 64
 65impl std::hash::Hash for Toolchain {
 66    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
 67        let Self {
 68            name,
 69            path,
 70            language_name,
 71            as_json: _,
 72        } = self;
 73        name.hash(state);
 74        path.hash(state);
 75        language_name.hash(state);
 76    }
 77}
 78
 79impl PartialEq for Toolchain {
 80    fn eq(&self, other: &Self) -> bool {
 81        let Self {
 82            name,
 83            path,
 84            language_name,
 85            as_json: _,
 86        } = self;
 87        // Do not use as_json for comparisons; it shouldn't impact equality, as it's not user-surfaced.
 88        // Thus, there could be multiple entries that look the same in the UI.
 89        (name, path, language_name).eq(&(&other.name, &other.path, &other.language_name))
 90    }
 91}
 92
 93#[async_trait]
 94pub trait ToolchainLister: Send + Sync + 'static {
 95    /// List all available toolchains for a given path.
 96    async fn list(
 97        &self,
 98        worktree_root: PathBuf,
 99        subroot_relative_path: Arc<RelPath>,
100        project_env: Option<HashMap<String, String>>,
101    ) -> ToolchainList;
102
103    /// Given a user-created toolchain, resolve lister-specific details.
104    /// Put another way: fill in the details of the toolchain so the user does not have to.
105    async fn resolve(
106        &self,
107        path: PathBuf,
108        project_env: Option<HashMap<String, String>>,
109    ) -> anyhow::Result<Toolchain>;
110
111    async fn activation_script(
112        &self,
113        toolchain: &Toolchain,
114        shell: ShellKind,
115        fs: &dyn Fs,
116    ) -> Vec<String>;
117    /// Returns various "static" bits of information about this toolchain lister. This function should be pure.
118    fn meta(&self) -> ToolchainMetadata;
119}
120
121#[derive(Clone, PartialEq, Eq, Hash)]
122pub struct ToolchainMetadata {
123    /// Returns a term which we should use in UI to refer to toolchains produced by a given `[ToolchainLister]`.
124    pub term: SharedString,
125    /// A user-facing placeholder describing the semantic meaning of a path to a new toolchain.
126    pub new_toolchain_placeholder: SharedString,
127    /// The name of the manifest file for this toolchain.
128    pub manifest_name: ManifestName,
129}
130
131#[async_trait(?Send)]
132pub trait LanguageToolchainStore: Send + Sync + 'static {
133    async fn active_toolchain(
134        self: Arc<Self>,
135        worktree_id: WorktreeId,
136        relative_path: Arc<RelPath>,
137        language_name: LanguageName,
138        cx: &mut AsyncApp,
139    ) -> Option<Toolchain>;
140}
141
142pub trait LocalLanguageToolchainStore: Send + Sync + 'static {
143    fn active_toolchain(
144        self: Arc<Self>,
145        worktree_id: WorktreeId,
146        relative_path: &Arc<RelPath>,
147        language_name: LanguageName,
148        cx: &mut AsyncApp,
149    ) -> Option<Toolchain>;
150}
151
152#[async_trait(?Send)]
153impl<T: LocalLanguageToolchainStore> LanguageToolchainStore for T {
154    async fn active_toolchain(
155        self: Arc<Self>,
156        worktree_id: WorktreeId,
157        relative_path: Arc<RelPath>,
158        language_name: LanguageName,
159        cx: &mut AsyncApp,
160    ) -> Option<Toolchain> {
161        self.active_toolchain(worktree_id, &relative_path, language_name, cx)
162    }
163}
164
165type DefaultIndex = usize;
166#[derive(Default, Clone, Debug)]
167pub struct ToolchainList {
168    pub toolchains: Vec<Toolchain>,
169    pub default: Option<DefaultIndex>,
170    pub groups: Box<[(usize, SharedString)]>,
171}
172
173impl ToolchainList {
174    pub fn toolchains(&self) -> &[Toolchain] {
175        &self.toolchains
176    }
177    pub fn default_toolchain(&self) -> Option<Toolchain> {
178        self.default.and_then(|ix| self.toolchains.get(ix)).cloned()
179    }
180    pub fn group_for_index(&self, index: usize) -> Option<(usize, SharedString)> {
181        if index >= self.toolchains.len() {
182            return None;
183        }
184        let first_equal_or_greater = self
185            .groups
186            .partition_point(|(group_lower_bound, _)| group_lower_bound <= &index);
187        self.groups
188            .get(first_equal_or_greater.checked_sub(1)?)
189            .cloned()
190    }
191}