Detailed changes
@@ -406,6 +406,7 @@ struct RunnableTasks {
templates: Vec<(TaskSourceKind, TaskTemplate)>,
// We need the column at which the task context evaluation should take place.
column: u32,
+ // Values of all named captures, including those starting with '_'
extra_variables: HashMap<String, String>,
}
@@ -3973,7 +3974,7 @@ impl Editor {
this.completion_tasks.clear();
this.discard_inline_completion(false, cx);
- let task_context = tasks.as_ref().zip(this.workspace.clone()).and_then(
+ let tasks = tasks.as_ref().zip(this.workspace.clone()).and_then(
|(tasks, (workspace, _))| {
let position = Point::new(buffer_row, tasks.1.column);
let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
@@ -3981,43 +3982,45 @@ impl Editor {
buffer: buffer.clone(),
range: range_start..range_start,
};
+ // Fill in the environmental variables from the tree-sitter captures
+ let mut captured_task_variables = TaskVariables::default();
+ for (capture_name, value) in tasks.1.extra_variables.clone() {
+ captured_task_variables.insert(
+ task::VariableName::Custom(capture_name.into()),
+ value.clone(),
+ );
+ }
+
workspace
.update(cx, |workspace, cx| {
- tasks::task_context_for_location(workspace, location, cx)
+ tasks::task_context_for_location(
+ captured_task_variables,
+ workspace,
+ location,
+ cx,
+ )
})
.ok()
.flatten()
+ .map(|task_context| {
+ Arc::new(ResolvedTasks {
+ templates: tasks
+ .1
+ .templates
+ .iter()
+ .filter_map(|(kind, template)| {
+ template
+ .resolve_task(&kind.to_id_base(), &task_context)
+ .map(|task| (kind.clone(), task))
+ })
+ .collect(),
+ position: snapshot.buffer_snapshot.anchor_before(
+ Point::new(multibuffer_point.row, tasks.1.column),
+ ),
+ })
+ })
},
);
- let tasks = tasks.zip(task_context).map(|(tasks, mut task_context)| {
- // Fill in the environmental variables from the tree-sitter captures
- let mut additional_task_variables = TaskVariables::default();
- for (capture_name, value) in tasks.1.extra_variables.clone() {
- additional_task_variables.insert(
- task::VariableName::Custom(capture_name.into()),
- value.clone(),
- );
- }
- task_context
- .task_variables
- .extend(additional_task_variables);
-
- Arc::new(ResolvedTasks {
- templates: tasks
- .1
- .templates
- .iter()
- .filter_map(|(kind, template)| {
- template
- .resolve_task(&kind.to_id_base(), &task_context)
- .map(|task| (kind.clone(), task))
- })
- .collect(),
- position: snapshot
- .buffer_snapshot
- .anchor_before(Point::new(multibuffer_point.row, tasks.1.column)),
- })
- });
let spawn_straight_away = tasks
.as_ref()
.map_or(false, |tasks| tasks.templates.len() == 1)
@@ -1,17 +1,16 @@
use crate::Editor;
-use std::{path::Path, sync::Arc};
-
use anyhow::Context;
-use gpui::WindowContext;
-use language::{BasicContextProvider, ContextProvider};
-use project::{Location, WorktreeId};
+use gpui::{Model, WindowContext};
+use language::ContextProvider;
+use project::{BasicContextProvider, Location, Project};
use task::{TaskContext, TaskVariables, VariableName};
use text::Point;
use util::ResultExt;
use workspace::Workspace;
pub(crate) fn task_context_for_location(
+ captured_variables: TaskVariables,
workspace: &Workspace,
location: Location,
cx: &mut WindowContext<'_>,
@@ -20,31 +19,16 @@ pub(crate) fn task_context_for_location(
.log_err()
.flatten();
- let buffer = location.buffer.clone();
- let language_context_provider = buffer
- .read(cx)
- .language()
- .and_then(|language| language.context_provider())
- .unwrap_or_else(|| Arc::new(BasicContextProvider));
-
- let worktree_abs_path = buffer
- .read(cx)
- .file()
- .map(|file| WorktreeId::from_usize(file.worktree_id()))
- .and_then(|worktree_id| {
- workspace
- .project()
- .read(cx)
- .worktree_for_id(worktree_id, cx)
- .map(|worktree| worktree.read(cx).abs_path())
- });
- let task_variables = combine_task_variables(
- worktree_abs_path.as_deref(),
+ let mut task_variables = combine_task_variables(
+ captured_variables,
location,
- language_context_provider.as_ref(),
+ workspace.project().clone(),
cx,
)
.log_err()?;
+ // Remove all custom entries starting with _, as they're not intended for use by the end user.
+ task_variables.sweep();
+
Some(TaskContext {
cwd,
task_variables,
@@ -84,21 +68,21 @@ fn task_context_with_editor(
buffer,
range: start..end,
};
- task_context_for_location(workspace, location.clone(), cx).map(|mut task_context| {
+ let captured_variables = {
+ let mut variables = TaskVariables::default();
for range in location
.buffer
.read(cx)
.snapshot()
- .runnable_ranges(location.range)
+ .runnable_ranges(location.range.clone())
{
for (capture_name, value) in range.extra_captures {
- task_context
- .task_variables
- .insert(VariableName::Custom(capture_name.into()), value);
+ variables.insert(VariableName::Custom(capture_name.into()), value);
}
}
- task_context
- })
+ variables
+ };
+ task_context_for_location(captured_variables, workspace, location.clone(), cx)
}
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
@@ -114,24 +98,26 @@ pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskCo
}
fn combine_task_variables(
- worktree_abs_path: Option<&Path>,
+ mut captured_variables: TaskVariables,
location: Location,
- context_provider: &dyn ContextProvider,
+ project: Model<Project>,
cx: &mut WindowContext<'_>,
) -> anyhow::Result<TaskVariables> {
- if context_provider.is_basic() {
- context_provider
- .build_context(worktree_abs_path, &location, cx)
- .context("building basic provider context")
- } else {
- let mut basic_context = BasicContextProvider
- .build_context(worktree_abs_path, &location, cx)
- .context("building basic default context")?;
- basic_context.extend(
- context_provider
- .build_context(worktree_abs_path, &location, cx)
+ let language_context_provider = location
+ .buffer
+ .read(cx)
+ .language()
+ .and_then(|language| language.context_provider());
+ let baseline = BasicContextProvider::new(project)
+ .build_context(&captured_variables, &location, cx)
+ .context("building basic default context")?;
+ captured_variables.extend(baseline);
+ if let Some(provider) = language_context_provider {
+ captured_variables.extend(
+ provider
+ .build_context(&captured_variables, &location, cx)
.context("building provider context ")?,
);
- Ok(basic_context)
}
+ Ok(captured_variables)
}
@@ -30,10 +30,10 @@ use gpui::{
};
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
use language::{
- ContextProviderWithTasks, LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry,
- QUERY_FILENAME_PREFIXES,
+ LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
};
use node_runtime::NodeRuntime;
+use project::ContextProviderWithTasks;
use semantic_version::SemanticVersion;
use serde::{Deserialize, Serialize};
use settings::Settings;
@@ -3024,6 +3024,7 @@ impl BufferSnapshot {
tags.sort_by_key(|(range, _)| range == &maximum_range);
let split_point = tags.partition_point(|(range, _)| range != &maximum_range);
let (extra_captures, tags) = tags.split_at(split_point);
+
let extra_captures = extra_captures
.into_iter()
.map(|(range, name)| {
@@ -58,9 +58,7 @@ use std::{
};
use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
use task::RunnableTag;
-pub use task_context::{
- BasicContextProvider, ContextProvider, ContextProviderWithTasks, RunnableRange,
-};
+pub use task_context::{ContextProvider, RunnableRange};
use theme::SyntaxTheme;
use tree_sitter::{self, wasmtime, Query, QueryCursor, WasmStore};
@@ -1016,7 +1014,7 @@ impl Language {
for (ix, name) in query.capture_names().iter().enumerate() {
if *name == "run" {
run_capture_index = Some(ix as u32);
- } else if !name.starts_with('_') {
+ } else {
runnable_tags.insert(ix as u32, RunnableTag(name.to_string().into()));
}
}
@@ -1,12 +1,12 @@
-use std::{ops::Range, path::Path};
+use std::ops::Range;
use crate::{Location, Runnable};
use anyhow::Result;
use collections::HashMap;
use gpui::AppContext;
-use task::{TaskTemplates, TaskVariables, VariableName};
-use text::{BufferId, Point, ToPoint};
+use task::{TaskTemplates, TaskVariables};
+use text::BufferId;
pub struct RunnableRange {
pub buffer_id: BufferId,
@@ -22,7 +22,7 @@ pub trait ContextProvider: Send + Sync {
/// Builds a specific context to be placed on top of the basic one (replacing all conflicting entries) and to be used for task resolving later.
fn build_context(
&self,
- _worktree_abs_path: Option<&Path>,
+ _variables: &TaskVariables,
_location: &Location,
_cx: &mut AppContext,
) -> Result<TaskVariables> {
@@ -33,100 +33,4 @@ pub trait ContextProvider: Send + Sync {
fn associated_tasks(&self) -> Option<TaskTemplates> {
None
}
-
- // Determines whether the [`BasicContextProvider`] variables should be filled too (if `false`), or omitted (if `true`).
- fn is_basic(&self) -> bool {
- false
- }
-}
-
-/// A context provided that tries to provide values for all non-custom [`VariableName`] variants for a currently opened file.
-/// Applied as a base for every custom [`ContextProvider`] unless explicitly oped out.
-pub struct BasicContextProvider;
-
-impl ContextProvider for BasicContextProvider {
- fn is_basic(&self) -> bool {
- true
- }
-
- fn build_context(
- &self,
- worktree_abs_path: Option<&Path>,
- location: &Location,
- cx: &mut AppContext,
- ) -> Result<TaskVariables> {
- let buffer = location.buffer.read(cx);
- let buffer_snapshot = buffer.snapshot();
- let symbols = buffer_snapshot.symbols_containing(location.range.start, None);
- let symbol = symbols.unwrap_or_default().last().map(|symbol| {
- let range = symbol
- .name_ranges
- .last()
- .cloned()
- .unwrap_or(0..symbol.text.len());
- symbol.text[range].to_string()
- });
-
- let current_file = buffer
- .file()
- .and_then(|file| file.as_local())
- .map(|file| file.abs_path(cx).to_string_lossy().to_string());
- let Point { row, column } = location.range.start.to_point(&buffer_snapshot);
- let row = row + 1;
- let column = column + 1;
- let selected_text = buffer
- .chars_for_range(location.range.clone())
- .collect::<String>();
-
- let mut task_variables = TaskVariables::from_iter([
- (VariableName::Row, row.to_string()),
- (VariableName::Column, column.to_string()),
- ]);
-
- if let Some(symbol) = symbol {
- task_variables.insert(VariableName::Symbol, symbol);
- }
- if !selected_text.trim().is_empty() {
- task_variables.insert(VariableName::SelectedText, selected_text);
- }
- if let Some(path) = current_file {
- task_variables.insert(VariableName::File, path);
- }
- if let Some(worktree_path) = worktree_abs_path {
- task_variables.insert(
- VariableName::WorktreeRoot,
- worktree_path.to_string_lossy().to_string(),
- );
- }
-
- Ok(task_variables)
- }
-}
-
-/// A ContextProvider that doesn't provide any task variables on it's own, though it has some associated tasks.
-pub struct ContextProviderWithTasks {
- templates: TaskTemplates,
-}
-
-impl ContextProviderWithTasks {
- pub fn new(definitions: TaskTemplates) -> Self {
- Self {
- templates: definitions,
- }
- }
-}
-
-impl ContextProvider for ContextProviderWithTasks {
- fn associated_tasks(&self) -> Option<TaskTemplates> {
- Some(self.templates.clone())
- }
-
- fn build_context(
- &self,
- worktree_abs_path: Option<&Path>,
- location: &Location,
- cx: &mut AppContext,
- ) -> Result<TaskVariables> {
- BasicContextProvider.build_context(worktree_abs_path, location, cx)
- }
}
@@ -1,4 +1,4 @@
-use language::ContextProviderWithTasks;
+use project::ContextProviderWithTasks;
use task::{TaskTemplate, TaskTemplates, VariableName};
pub(super) fn bash_task_context() -> ContextProviderWithTasks {
@@ -16,7 +16,7 @@ use std::{
borrow::Cow,
ffi::{OsStr, OsString},
ops::Range,
- path::{Path, PathBuf},
+ path::PathBuf,
str,
sync::{
atomic::{AtomicBool, Ordering::SeqCst},
@@ -447,7 +447,7 @@ const GO_PACKAGE_TASK_VARIABLE: VariableName = VariableName::Custom(Cow::Borrowe
impl ContextProvider for GoContextProvider {
fn build_context(
&self,
- worktree_abs_path: Option<&Path>,
+ variables: &TaskVariables,
location: &Location,
cx: &mut gpui::AppContext,
) -> Result<TaskVariables> {
@@ -465,7 +465,8 @@ impl ContextProvider for GoContextProvider {
// Prefer the relative form `./my-nested-package/is-here` over
// absolute path, because it's more readable in the modal, but
// the absolute path also works.
- let package_name = worktree_abs_path
+ let package_name = variables
+ .get(&VariableName::WorktreeRoot)
.and_then(|worktree_abs_path| buffer_dir.strip_prefix(worktree_abs_path).ok())
.map(|relative_pkg_dir| {
if relative_pkg_dir.as_os_str().is_empty() {
@@ -1,8 +1,9 @@
use anyhow::Result;
use async_trait::async_trait;
-use language::{ContextProviderWithTasks, LanguageServerName, LspAdapter, LspAdapterDelegate};
+use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::LanguageServerBinary;
use node_runtime::NodeRuntime;
+use project::ContextProviderWithTasks;
use std::{
any::Any,
ffi::OsString,
@@ -328,7 +328,7 @@ const RUST_PACKAGE_TASK_VARIABLE: VariableName =
impl ContextProvider for RustContextProvider {
fn build_context(
&self,
- _: Option<&Path>,
+ _: &TaskVariables,
location: &Location,
cx: &mut gpui::AppContext,
) -> Result<TaskVariables> {
@@ -113,7 +113,9 @@ pub use fs::*;
pub use language::Location;
#[cfg(any(test, feature = "test-support"))]
pub use prettier::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX;
-pub use task_inventory::{Inventory, TaskSourceKind};
+pub use task_inventory::{
+ BasicContextProvider, ContextProviderWithTasks, Inventory, TaskSourceKind,
+};
pub use worktree::{
DiagnosticSummary, Entry, EntryKind, File, LocalWorktree, PathChange, ProjectEntryId,
RepositoryEntry, UpdatedEntriesSet, UpdatedGitRepositoriesSet, Worktree, WorktreeId,
@@ -6,6 +6,7 @@ use std::{
sync::Arc,
};
+use anyhow::Result;
use collections::{btree_map, BTreeMap, VecDeque};
use futures::{
channel::mpsc::{unbounded, UnboundedSender},
@@ -13,13 +14,17 @@ use futures::{
};
use gpui::{AppContext, Context, Model, ModelContext, Task};
use itertools::Itertools;
-use language::Language;
+use language::{ContextProvider, Language, Location};
use task::{
- static_source::StaticSource, ResolvedTask, TaskContext, TaskId, TaskTemplate, VariableName,
+ static_source::StaticSource, ResolvedTask, TaskContext, TaskId, TaskTemplate, TaskTemplates,
+ TaskVariables, VariableName,
};
+use text::{Point, ToPoint};
use util::{post_inc, NumericPrefixWithSuffix};
use worktree::WorktreeId;
+use crate::Project;
+
/// Inventory tracks available tasks for a given project.
pub struct Inventory {
sources: Vec<SourceInInventory>,
@@ -491,6 +496,102 @@ mod test_inventory {
}
}
+/// A context provided that tries to provide values for all non-custom [`VariableName`] variants for a currently opened file.
+/// Applied as a base for every custom [`ContextProvider`] unless explicitly oped out.
+pub struct BasicContextProvider {
+ project: Model<Project>,
+}
+
+impl BasicContextProvider {
+ pub fn new(project: Model<Project>) -> Self {
+ Self { project }
+ }
+}
+
+impl ContextProvider for BasicContextProvider {
+ fn build_context(
+ &self,
+ _: &TaskVariables,
+ location: &Location,
+ cx: &mut AppContext,
+ ) -> Result<TaskVariables> {
+ let buffer = location.buffer.read(cx);
+ let buffer_snapshot = buffer.snapshot();
+ let symbols = buffer_snapshot.symbols_containing(location.range.start, None);
+ let symbol = symbols.unwrap_or_default().last().map(|symbol| {
+ let range = symbol
+ .name_ranges
+ .last()
+ .cloned()
+ .unwrap_or(0..symbol.text.len());
+ symbol.text[range].to_string()
+ });
+
+ let current_file = buffer
+ .file()
+ .and_then(|file| file.as_local())
+ .map(|file| file.abs_path(cx).to_string_lossy().to_string());
+ let Point { row, column } = location.range.start.to_point(&buffer_snapshot);
+ let row = row + 1;
+ let column = column + 1;
+ let selected_text = buffer
+ .chars_for_range(location.range.clone())
+ .collect::<String>();
+
+ let mut task_variables = TaskVariables::from_iter([
+ (VariableName::Row, row.to_string()),
+ (VariableName::Column, column.to_string()),
+ ]);
+
+ if let Some(symbol) = symbol {
+ task_variables.insert(VariableName::Symbol, symbol);
+ }
+ if !selected_text.trim().is_empty() {
+ task_variables.insert(VariableName::SelectedText, selected_text);
+ }
+ if let Some(path) = current_file {
+ task_variables.insert(VariableName::File, path);
+ }
+
+ let worktree_abs_path = buffer
+ .file()
+ .map(|file| WorktreeId::from_usize(file.worktree_id()))
+ .and_then(|worktree_id| {
+ self.project
+ .read(cx)
+ .worktree_for_id(worktree_id, cx)
+ .map(|worktree| worktree.read(cx).abs_path())
+ });
+ if let Some(worktree_path) = worktree_abs_path {
+ task_variables.insert(
+ VariableName::WorktreeRoot,
+ worktree_path.to_string_lossy().to_string(),
+ );
+ }
+
+ Ok(task_variables)
+ }
+}
+
+/// A ContextProvider that doesn't provide any task variables on it's own, though it has some associated tasks.
+pub struct ContextProviderWithTasks {
+ templates: TaskTemplates,
+}
+
+impl ContextProviderWithTasks {
+ pub fn new(definitions: TaskTemplates) -> Self {
+ Self {
+ templates: definitions,
+ }
+ }
+}
+
+impl ContextProvider for ContextProviderWithTasks {
+ fn associated_tasks(&self) -> Option<TaskTemplates> {
+ Some(self.templates.clone())
+ }
+}
+
#[cfg(test)]
mod tests {
use gpui::TestAppContext;
@@ -185,6 +185,20 @@ impl TaskVariables {
pub fn extend(&mut self, other: Self) {
self.0.extend(other.0);
}
+ /// Get the value associated with given variable name, if there is one.
+ pub fn get(&self, key: &VariableName) -> Option<&str> {
+ self.0.get(key).map(|s| s.as_str())
+ }
+ /// Clear out variables obtained from tree-sitter queries, which are prefixed with '_' character
+ pub fn sweep(&mut self) {
+ self.0.retain(|name, _| {
+ if let VariableName::Custom(name) = name {
+ !name.starts_with('_')
+ } else {
+ true
+ }
+ })
+ }
}
impl FromIterator<(VariableName, String)> for TaskVariables {
@@ -153,8 +153,8 @@ mod tests {
use editor::Editor;
use gpui::{Entity, TestAppContext};
- use language::{BasicContextProvider, Language, LanguageConfig};
- use project::{FakeFs, Project};
+ use language::{Language, LanguageConfig};
+ use project::{BasicContextProvider, FakeFs, Project};
use serde_json::json;
use task::{TaskContext, TaskVariables, VariableName};
use ui::VisualContext;
@@ -191,7 +191,7 @@ mod tests {
}),
)
.await;
-
+ let project = Project::test(fs, ["/dir".as_ref()], cx).await;
let rust_language = Arc::new(
Language::new(
LanguageConfig::default(),
@@ -203,7 +203,7 @@ mod tests {
name: (_) @name) @item"#,
)
.unwrap()
- .with_context_provider(Some(Arc::new(BasicContextProvider))),
+ .with_context_provider(Some(Arc::new(BasicContextProvider::new(project.clone())))),
);
let typescript_language = Arc::new(
@@ -221,9 +221,9 @@ mod tests {
")" @context)) @item"#,
)
.unwrap()
- .with_context_provider(Some(Arc::new(BasicContextProvider))),
+ .with_context_provider(Some(Arc::new(BasicContextProvider::new(project.clone())))),
);
- let project = Project::test(fs, ["/dir".as_ref()], cx).await;
+
let worktree_id = project.update(cx, |project, cx| {
project.worktrees().next().unwrap().read(cx).id()
});
@@ -540,8 +540,8 @@ mod tests {
use editor::Editor;
use gpui::{TestAppContext, VisualTestContext};
- use language::{ContextProviderWithTasks, Language, LanguageConfig, LanguageMatcher, Point};
- use project::{FakeFs, Project};
+ use language::{Language, LanguageConfig, LanguageMatcher, Point};
+ use project::{ContextProviderWithTasks, FakeFs, Project};
use serde_json::json;
use task::TaskTemplates;
use workspace::CloseInactiveTabsAndPanes;