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:
parent
03bd95405b
commit
cdddb4d360
33 changed files with 2221 additions and 133 deletions
|
@ -15,6 +15,7 @@ mod outline;
|
|||
pub mod proto;
|
||||
mod syntax_map;
|
||||
mod task_context;
|
||||
mod toolchain;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod buffer_tests;
|
||||
|
@ -28,7 +29,7 @@ use futures::Future;
|
|||
use gpui::{AppContext, AsyncAppContext, Model, SharedString, Task};
|
||||
pub use highlight_map::HighlightMap;
|
||||
use http_client::HttpClient;
|
||||
pub use language_registry::LanguageName;
|
||||
pub use language_registry::{LanguageName, LoadedLanguage};
|
||||
use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerBinaryOptions};
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
|
@ -61,6 +62,7 @@ use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
|
|||
use task::RunnableTag;
|
||||
pub use task_context::{ContextProvider, RunnableRange};
|
||||
use theme::SyntaxTheme;
|
||||
pub use toolchain::{LanguageToolchainStore, Toolchain, ToolchainList, ToolchainLister};
|
||||
use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};
|
||||
use util::serde::default_true;
|
||||
|
||||
|
@ -502,6 +504,7 @@ pub trait LspAdapter: 'static + Send + Sync {
|
|||
async fn workspace_configuration(
|
||||
self: Arc<Self>,
|
||||
_: &Arc<dyn LspAdapterDelegate>,
|
||||
_: Arc<dyn LanguageToolchainStore>,
|
||||
_cx: &mut AsyncAppContext,
|
||||
) -> Result<Value> {
|
||||
Ok(serde_json::json!({}))
|
||||
|
@ -855,6 +858,7 @@ pub struct Language {
|
|||
pub(crate) config: LanguageConfig,
|
||||
pub(crate) grammar: Option<Arc<Grammar>>,
|
||||
pub(crate) context_provider: Option<Arc<dyn ContextProvider>>,
|
||||
pub(crate) toolchain: Option<Arc<dyn ToolchainLister>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
|
@ -983,6 +987,7 @@ impl Language {
|
|||
})
|
||||
}),
|
||||
context_provider: None,
|
||||
toolchain: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -991,6 +996,11 @@ impl Language {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_toolchain_lister(mut self, provider: Option<Arc<dyn ToolchainLister>>) -> Self {
|
||||
self.toolchain = provider;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_queries(mut self, queries: LanguageQueries) -> Result<Self> {
|
||||
if let Some(query) = queries.highlights {
|
||||
self = self
|
||||
|
@ -1361,6 +1371,10 @@ impl Language {
|
|||
self.context_provider.clone()
|
||||
}
|
||||
|
||||
pub fn toolchain_lister(&self) -> Option<Arc<dyn ToolchainLister>> {
|
||||
self.toolchain.clone()
|
||||
}
|
||||
|
||||
pub fn highlight_text<'a>(
|
||||
self: &'a Arc<Self>,
|
||||
text: &'a Rope,
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
|||
},
|
||||
task_context::ContextProvider,
|
||||
with_parser, CachedLspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher,
|
||||
LanguageServerName, LspAdapter, PLAIN_TEXT,
|
||||
LanguageServerName, LspAdapter, ToolchainLister, PLAIN_TEXT,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
|
@ -75,6 +75,13 @@ impl<'a> From<&'a str> for LanguageName {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<LanguageName> for String {
|
||||
fn from(value: LanguageName) -> Self {
|
||||
let value: &str = &value.0;
|
||||
Self::from(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LanguageRegistry {
|
||||
state: RwLock<LanguageRegistryState>,
|
||||
language_server_download_dir: Option<Arc<Path>>,
|
||||
|
@ -123,16 +130,7 @@ pub struct AvailableLanguage {
|
|||
name: LanguageName,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<
|
||||
dyn Fn() -> Result<(
|
||||
LanguageConfig,
|
||||
LanguageQueries,
|
||||
Option<Arc<dyn ContextProvider>>,
|
||||
)>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
|
||||
loaded: bool,
|
||||
}
|
||||
|
||||
|
@ -200,6 +198,13 @@ struct LspBinaryStatusSender {
|
|||
txs: Arc<Mutex<Vec<mpsc::UnboundedSender<(LanguageServerName, LanguageServerBinaryStatus)>>>>,
|
||||
}
|
||||
|
||||
pub struct LoadedLanguage {
|
||||
pub config: LanguageConfig,
|
||||
pub queries: LanguageQueries,
|
||||
pub context_provider: Option<Arc<dyn ContextProvider>>,
|
||||
pub toolchain_provider: Option<Arc<dyn ToolchainLister>>,
|
||||
}
|
||||
|
||||
impl LanguageRegistry {
|
||||
pub fn new(executor: BackgroundExecutor) -> Self {
|
||||
let this = Self {
|
||||
|
@ -283,7 +288,14 @@ impl LanguageRegistry {
|
|||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
move || Ok((config.clone(), Default::default(), None)),
|
||||
move || {
|
||||
Ok(LoadedLanguage {
|
||||
config: config.clone(),
|
||||
queries: Default::default(),
|
||||
toolchain_provider: None,
|
||||
context_provider: None,
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -424,14 +436,7 @@ impl LanguageRegistry {
|
|||
name: LanguageName,
|
||||
grammar_name: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: impl Fn() -> Result<(
|
||||
LanguageConfig,
|
||||
LanguageQueries,
|
||||
Option<Arc<dyn ContextProvider>>,
|
||||
)>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync,
|
||||
load: impl Fn() -> Result<LoadedLanguage> + 'static + Send + Sync,
|
||||
) {
|
||||
let load = Arc::new(load);
|
||||
let state = &mut *self.state.write();
|
||||
|
@ -726,16 +731,18 @@ impl LanguageRegistry {
|
|||
self.executor
|
||||
.spawn(async move {
|
||||
let language = async {
|
||||
let (config, queries, provider) = (language_load)()?;
|
||||
|
||||
if let Some(grammar) = config.grammar.clone() {
|
||||
let loaded_language = (language_load)()?;
|
||||
if let Some(grammar) = loaded_language.config.grammar.clone() {
|
||||
let grammar = Some(this.get_or_load_grammar(grammar).await?);
|
||||
Language::new_with_id(id, config, grammar)
|
||||
.with_context_provider(provider)
|
||||
.with_queries(queries)
|
||||
|
||||
Language::new_with_id(id, loaded_language.config, grammar)
|
||||
.with_context_provider(loaded_language.context_provider)
|
||||
.with_toolchain_lister(loaded_language.toolchain_provider)
|
||||
.with_queries(loaded_language.queries)
|
||||
} else {
|
||||
Ok(Language::new_with_id(id, config, None)
|
||||
.with_context_provider(provider))
|
||||
Ok(Language::new_with_id(id, loaded_language.config, None)
|
||||
.with_context_provider(loaded_language.context_provider)
|
||||
.with_toolchain_lister(loaded_language.toolchain_provider))
|
||||
}
|
||||
}
|
||||
.await;
|
||||
|
|
65
crates/language/src/toolchain.rs
Normal file
65
crates/language/src/toolchain.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
//! Provides support for language toolchains.
|
||||
//!
|
||||
//! A language can have associated toolchains,
|
||||
//! 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 async_trait::async_trait;
|
||||
use gpui::{AsyncAppContext, SharedString};
|
||||
use settings::WorktreeId;
|
||||
|
||||
use crate::LanguageName;
|
||||
|
||||
/// Represents a single toolchain.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Toolchain {
|
||||
/// User-facing label
|
||||
pub name: SharedString,
|
||||
pub path: SharedString,
|
||||
pub language_name: LanguageName,
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait ToolchainLister: Send + Sync {
|
||||
async fn list(&self, _: PathBuf) -> ToolchainList;
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
pub trait LanguageToolchainStore {
|
||||
async fn active_toolchain(
|
||||
self: Arc<Self>,
|
||||
worktree_id: WorktreeId,
|
||||
language_name: LanguageName,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Option<Toolchain>;
|
||||
}
|
||||
|
||||
type DefaultIndex = usize;
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ToolchainList {
|
||||
pub toolchains: Vec<Toolchain>,
|
||||
pub default: Option<DefaultIndex>,
|
||||
pub groups: Box<[(usize, SharedString)]>,
|
||||
}
|
||||
|
||||
impl ToolchainList {
|
||||
pub fn toolchains(&self) -> &[Toolchain] {
|
||||
&self.toolchains
|
||||
}
|
||||
pub fn default_toolchain(&self) -> Option<Toolchain> {
|
||||
self.default.and_then(|ix| self.toolchains.get(ix)).cloned()
|
||||
}
|
||||
pub fn group_for_index(&self, index: usize) -> Option<(usize, SharedString)> {
|
||||
if index >= self.toolchains.len() {
|
||||
return None;
|
||||
}
|
||||
let first_equal_or_greater = self
|
||||
.groups
|
||||
.partition_point(|(group_lower_bound, _)| group_lower_bound <= &index);
|
||||
self.groups
|
||||
.get(first_equal_or_greater.checked_sub(1)?)
|
||||
.cloned()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue