Extensions registering tasks (#9572)
This PR also introduces built-in tasks for Rust and Elixir. Note that this is not a precedent for future PRs to include tasks for more languages; we simply want to find the rough edges with tasks & language integrations before proceeding to task contexts provided by extensions. As is, we'll load tasks for all loaded languages, so in order to get Elixir tasks, you have to open an Elixir buffer first. I think it sort of makes sense (though it's not ideal), as in the future where extensions do provide their own tasks.json, we'd like to limit the # of tasks surfaced to the user to make them as relevant to the project at hand as possible. Release Notes: - Added built-in tasks for Rust and Elixir files.
This commit is contained in:
parent
cb4f868815
commit
4dc61f7ccd
19 changed files with 416 additions and 169 deletions
|
@ -14,6 +14,7 @@ pub mod language_settings;
|
|||
mod outline;
|
||||
pub mod proto;
|
||||
mod syntax_map;
|
||||
mod task_context;
|
||||
|
||||
#[cfg(test)]
|
||||
mod buffer_tests;
|
||||
|
@ -54,6 +55,9 @@ use std::{
|
|||
},
|
||||
};
|
||||
use syntax_map::SyntaxSnapshot;
|
||||
pub use task_context::{
|
||||
ContextProvider, ContextProviderWithTasks, LanguageSource, SymbolContextProvider,
|
||||
};
|
||||
use theme::SyntaxTheme;
|
||||
use tree_sitter::{self, wasmtime, Query, WasmStore};
|
||||
use util::http::HttpClient;
|
||||
|
@ -120,46 +124,6 @@ pub struct Location {
|
|||
pub range: Range<Anchor>,
|
||||
}
|
||||
|
||||
pub struct LanguageContext {
|
||||
pub package: Option<String>,
|
||||
pub symbol: Option<String>,
|
||||
}
|
||||
|
||||
pub trait LanguageContextProvider: Send + Sync {
|
||||
fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<LanguageContext>;
|
||||
}
|
||||
|
||||
/// A context provider that fills out LanguageContext without inspecting the contents.
|
||||
pub struct DefaultContextProvider;
|
||||
|
||||
impl LanguageContextProvider for DefaultContextProvider {
|
||||
fn build_context(
|
||||
&self,
|
||||
location: Location,
|
||||
cx: &mut AppContext,
|
||||
) -> gpui::Result<LanguageContext> {
|
||||
let symbols = location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.symbols_containing(location.range.start, None);
|
||||
let symbol = symbols.and_then(|symbols| {
|
||||
symbols.last().map(|symbol| {
|
||||
let range = symbol
|
||||
.name_ranges
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or(0..symbol.text.len());
|
||||
symbol.text[range].to_string()
|
||||
})
|
||||
});
|
||||
Ok(LanguageContext {
|
||||
package: None,
|
||||
symbol,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a Language Server, with certain cached sync properties.
|
||||
/// Uses [`LspAdapter`] under the hood, but calls all 'static' methods
|
||||
/// once at startup, and caches the results.
|
||||
|
@ -777,7 +741,7 @@ pub struct Language {
|
|||
pub(crate) id: LanguageId,
|
||||
pub(crate) config: LanguageConfig,
|
||||
pub(crate) grammar: Option<Arc<Grammar>>,
|
||||
pub(crate) context_provider: Option<Arc<dyn LanguageContextProvider>>,
|
||||
pub(crate) context_provider: Option<Arc<dyn ContextProvider>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
|
@ -892,10 +856,7 @@ impl Language {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_context_provider(
|
||||
mut self,
|
||||
provider: Option<Arc<dyn LanguageContextProvider>>,
|
||||
) -> Self {
|
||||
pub fn with_context_provider(mut self, provider: Option<Arc<dyn ContextProvider>>) -> Self {
|
||||
self.context_provider = provider;
|
||||
self
|
||||
}
|
||||
|
@ -1220,7 +1181,7 @@ impl Language {
|
|||
self.config.name.clone()
|
||||
}
|
||||
|
||||
pub fn context_provider(&self) -> Option<Arc<dyn LanguageContextProvider>> {
|
||||
pub fn context_provider(&self) -> Option<Arc<dyn ContextProvider>> {
|
||||
self.context_provider.clone()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
language_settings::all_language_settings, CachedLspAdapter, File, Language, LanguageConfig,
|
||||
LanguageContextProvider, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter,
|
||||
language_settings::all_language_settings, task_context::ContextProvider, CachedLspAdapter,
|
||||
File, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter,
|
||||
LspAdapterDelegate, PARSER, PLAIN_TEXT,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
|
@ -73,9 +73,17 @@ struct AvailableLanguage {
|
|||
name: Arc<str>,
|
||||
grammar: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
load: Arc<dyn Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync>,
|
||||
load: Arc<
|
||||
dyn Fn() -> Result<(
|
||||
LanguageConfig,
|
||||
LanguageQueries,
|
||||
Option<Arc<dyn ContextProvider>>,
|
||||
)>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
loaded: bool,
|
||||
context_provider: Option<Arc<dyn LanguageContextProvider>>,
|
||||
}
|
||||
|
||||
enum AvailableGrammar {
|
||||
|
@ -195,8 +203,7 @@ impl LanguageRegistry {
|
|||
config.name.clone(),
|
||||
config.grammar.clone(),
|
||||
config.matcher.clone(),
|
||||
None,
|
||||
move || Ok((config.clone(), Default::default())),
|
||||
move || Ok((config.clone(), Default::default(), None)),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -245,8 +252,14 @@ impl LanguageRegistry {
|
|||
name: Arc<str>,
|
||||
grammar_name: Option<Arc<str>>,
|
||||
matcher: LanguageMatcher,
|
||||
context_provider: Option<Arc<dyn LanguageContextProvider>>,
|
||||
load: impl Fn() -> Result<(LanguageConfig, LanguageQueries)> + 'static + Send + Sync,
|
||||
load: impl Fn() -> Result<(
|
||||
LanguageConfig,
|
||||
LanguageQueries,
|
||||
Option<Arc<dyn ContextProvider>>,
|
||||
)>
|
||||
+ 'static
|
||||
+ Send
|
||||
+ Sync,
|
||||
) {
|
||||
let load = Arc::new(load);
|
||||
let state = &mut *self.state.write();
|
||||
|
@ -266,8 +279,6 @@ impl LanguageRegistry {
|
|||
grammar: grammar_name,
|
||||
matcher,
|
||||
load,
|
||||
|
||||
context_provider,
|
||||
loaded: false,
|
||||
});
|
||||
state.version += 1;
|
||||
|
@ -333,7 +344,6 @@ impl LanguageRegistry {
|
|||
matcher: language.config.matcher.clone(),
|
||||
load: Arc::new(|| Err(anyhow!("already loaded"))),
|
||||
loaded: true,
|
||||
context_provider: language.context_provider.clone(),
|
||||
});
|
||||
state.add(language);
|
||||
}
|
||||
|
@ -507,9 +517,8 @@ impl LanguageRegistry {
|
|||
.spawn(async move {
|
||||
let id = language.id;
|
||||
let name = language.name.clone();
|
||||
let provider = language.context_provider.clone();
|
||||
let language = async {
|
||||
let (config, queries) = (language.load)()?;
|
||||
let (config, queries, provider) = (language.load)()?;
|
||||
|
||||
if let Some(grammar) = config.grammar.clone() {
|
||||
let grammar = Some(this.get_or_load_grammar(grammar).await?);
|
||||
|
|
109
crates/language/src/task_context.rs
Normal file
109
crates/language/src/task_context.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use crate::{LanguageRegistry, Location};
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{AppContext, Context, Model};
|
||||
use std::sync::Arc;
|
||||
use task::{static_source::tasks_for, static_source::TaskDefinitions, TaskSource, TaskVariables};
|
||||
|
||||
/// Language Contexts are used by Zed tasks to extract information about source file.
|
||||
pub trait ContextProvider: Send + Sync {
|
||||
fn build_context(&self, _: Location, _: &mut AppContext) -> Result<TaskVariables> {
|
||||
Ok(TaskVariables::default())
|
||||
}
|
||||
fn associated_tasks(&self) -> Option<TaskDefinitions> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A context provider that finds out what symbol is currently focused in the buffer.
|
||||
pub struct SymbolContextProvider;
|
||||
|
||||
impl ContextProvider for SymbolContextProvider {
|
||||
fn build_context(
|
||||
&self,
|
||||
location: Location,
|
||||
cx: &mut AppContext,
|
||||
) -> gpui::Result<TaskVariables> {
|
||||
let symbols = location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.symbols_containing(location.range.start, None);
|
||||
let symbol = symbols.and_then(|symbols| {
|
||||
symbols.last().map(|symbol| {
|
||||
let range = symbol
|
||||
.name_ranges
|
||||
.last()
|
||||
.cloned()
|
||||
.unwrap_or(0..symbol.text.len());
|
||||
symbol.text[range].to_string()
|
||||
})
|
||||
});
|
||||
Ok(TaskVariables::from_iter(
|
||||
symbol.map(|symbol| ("ZED_SYMBOL".to_string(), symbol)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A ContextProvider that doesn't provide any task variables on it's own, though it has some associated tasks.
|
||||
pub struct ContextProviderWithTasks {
|
||||
definitions: TaskDefinitions,
|
||||
}
|
||||
|
||||
impl ContextProviderWithTasks {
|
||||
pub fn new(definitions: TaskDefinitions) -> Self {
|
||||
Self { definitions }
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextProvider for ContextProviderWithTasks {
|
||||
fn associated_tasks(&self) -> Option<TaskDefinitions> {
|
||||
Some(self.definitions.clone())
|
||||
}
|
||||
|
||||
fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<TaskVariables> {
|
||||
SymbolContextProvider.build_context(location, cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// A source that pulls in the tasks from language registry.
|
||||
pub struct LanguageSource {
|
||||
languages: Arc<LanguageRegistry>,
|
||||
}
|
||||
|
||||
impl LanguageSource {
|
||||
pub fn new(
|
||||
languages: Arc<LanguageRegistry>,
|
||||
cx: &mut AppContext,
|
||||
) -> Model<Box<dyn TaskSource>> {
|
||||
cx.new_model(|_| Box::new(Self { languages }) as Box<_>)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskSource for LanguageSource {
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn tasks_for_path(
|
||||
&mut self,
|
||||
_: Option<&std::path::Path>,
|
||||
_: &mut gpui::ModelContext<Box<dyn TaskSource>>,
|
||||
) -> Vec<Arc<dyn task::Task>> {
|
||||
self.languages
|
||||
.to_vec()
|
||||
.into_iter()
|
||||
.filter_map(|language| {
|
||||
language
|
||||
.context_provider()?
|
||||
.associated_tasks()
|
||||
.map(|tasks| (tasks, language))
|
||||
})
|
||||
.flat_map(|(tasks, language)| {
|
||||
let language_name = language.name();
|
||||
let id_base = format!("buffer_source_{language_name}");
|
||||
tasks_for(tasks, &id_base)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue