Merge Zed task context providing logic (#10544)
Before, `tasks_ui` set most of the context with `SymbolContextProvider` providing the symbol data part of the context. Now, there's a `BasicContextProvider` that forms all standard Zed context and it automatically serves as a base, with no need for other providers like `RustContextProvider` to call it as before. Also, stop adding `SelectedText` task variable into the context for blank text selection. Release Notes: - N/A
This commit is contained in:
parent
97c5cffbe3
commit
573ba83034
5 changed files with 173 additions and 124 deletions
|
@ -56,7 +56,7 @@ use std::{
|
|||
},
|
||||
};
|
||||
use syntax_map::SyntaxSnapshot;
|
||||
pub use task_context::{ContextProvider, ContextProviderWithTasks, SymbolContextProvider};
|
||||
pub use task_context::{BasicContextProvider, ContextProvider, ContextProviderWithTasks};
|
||||
use theme::SyntaxTheme;
|
||||
use tree_sitter::{self, wasmtime, Query, WasmStore};
|
||||
use util::http::HttpClient;
|
||||
|
|
|
@ -1,34 +1,56 @@
|
|||
use std::path::Path;
|
||||
|
||||
use crate::Location;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::AppContext;
|
||||
use task::{TaskTemplates, TaskVariables, VariableName};
|
||||
use text::{Point, ToPoint};
|
||||
|
||||
/// Language Contexts are used by Zed tasks to extract information about source file.
|
||||
/// Language Contexts are used by Zed tasks to extract information about the source file where the tasks are supposed to be scheduled from.
|
||||
/// Multiple context providers may be used together: by default, Zed provides a base [`BasicContextProvider`] context that fills all non-custom [`VariableName`] variants.
|
||||
///
|
||||
/// The context will be used to fill data for the tasks, and filter out the ones that do not have the variables required.
|
||||
pub trait ContextProvider: Send + Sync {
|
||||
fn build_context(&self, _: Location, _: &mut AppContext) -> Result<TaskVariables> {
|
||||
/// 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,
|
||||
_: Option<&Path>,
|
||||
_: &Location,
|
||||
_: &mut AppContext,
|
||||
) -> Result<TaskVariables> {
|
||||
Ok(TaskVariables::default())
|
||||
}
|
||||
|
||||
/// Provides all tasks, associated with the current language.
|
||||
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 provider that finds out what symbol is currently focused in the buffer.
|
||||
pub struct SymbolContextProvider;
|
||||
/// 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
|
||||
}
|
||||
|
||||
impl ContextProvider for SymbolContextProvider {
|
||||
fn build_context(
|
||||
&self,
|
||||
location: Location,
|
||||
worktree_abs_path: Option<&Path>,
|
||||
location: &Location,
|
||||
cx: &mut AppContext,
|
||||
) -> gpui::Result<TaskVariables> {
|
||||
let symbols = location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.symbols_containing(location.range.start, None);
|
||||
) -> 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
|
||||
|
@ -37,9 +59,40 @@ impl ContextProvider for SymbolContextProvider {
|
|||
.unwrap_or(0..symbol.text.len());
|
||||
symbol.text[range].to_string()
|
||||
});
|
||||
Ok(TaskVariables::from_iter(
|
||||
Some(VariableName::Symbol).zip(symbol),
|
||||
))
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +114,12 @@ impl ContextProvider for ContextProviderWithTasks {
|
|||
Some(self.templates.clone())
|
||||
}
|
||||
|
||||
fn build_context(&self, location: Location, cx: &mut AppContext) -> Result<TaskVariables> {
|
||||
SymbolContextProvider.build_context(location, cx)
|
||||
fn build_context(
|
||||
&self,
|
||||
worktree_abs_path: Option<&Path>,
|
||||
location: &Location,
|
||||
cx: &mut AppContext,
|
||||
) -> Result<TaskVariables> {
|
||||
BasicContextProvider.build_context(worktree_abs_path, location, cx)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ pub fn init(
|
|||
Ok((
|
||||
config.clone(),
|
||||
load_queries($name),
|
||||
Some(Arc::new(language::SymbolContextProvider)),
|
||||
Some(Arc::new(language::BasicContextProvider)),
|
||||
))
|
||||
},
|
||||
);
|
||||
|
@ -107,7 +107,7 @@ pub fn init(
|
|||
Ok((
|
||||
config.clone(),
|
||||
load_queries($name),
|
||||
Some(Arc::new(language::SymbolContextProvider)),
|
||||
Some(Arc::new(language::BasicContextProvider)),
|
||||
))
|
||||
},
|
||||
);
|
||||
|
|
|
@ -331,25 +331,26 @@ const RUST_PACKAGE_TASK_VARIABLE: VariableName =
|
|||
impl ContextProvider for RustContextProvider {
|
||||
fn build_context(
|
||||
&self,
|
||||
location: Location,
|
||||
_: Option<&Path>,
|
||||
location: &Location,
|
||||
cx: &mut gpui::AppContext,
|
||||
) -> Result<TaskVariables> {
|
||||
let mut context = SymbolContextProvider.build_context(location.clone(), cx)?;
|
||||
|
||||
let local_abs_path = location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.and_then(|file| Some(file.as_local()?.abs_path(cx)));
|
||||
if let Some(package_name) = local_abs_path
|
||||
.as_deref()
|
||||
.and_then(|local_abs_path| local_abs_path.parent())
|
||||
.and_then(human_readable_package_name)
|
||||
{
|
||||
context.insert(RUST_PACKAGE_TASK_VARIABLE.clone(), package_name);
|
||||
}
|
||||
|
||||
Ok(context)
|
||||
Ok(
|
||||
if let Some(package_name) = local_abs_path
|
||||
.as_deref()
|
||||
.and_then(|local_abs_path| local_abs_path.parent())
|
||||
.and_then(human_readable_package_name)
|
||||
{
|
||||
TaskVariables::from_iter(Some((RUST_PACKAGE_TASK_VARIABLE.clone(), package_name)))
|
||||
} else {
|
||||
TaskVariables::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn associated_tasks(&self) -> Option<TaskTemplates> {
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use ::settings::Settings;
|
||||
use anyhow::Context;
|
||||
use editor::Editor;
|
||||
use gpui::{AppContext, ViewContext, WindowContext};
|
||||
use language::{Language, Point};
|
||||
use language::{BasicContextProvider, ContextProvider, Language};
|
||||
use modal::{Spawn, TasksModal};
|
||||
use project::{Location, TaskSourceKind, WorktreeId};
|
||||
use task::{ResolvedTask, TaskContext, TaskTemplate, TaskVariables, VariableName};
|
||||
use task::{ResolvedTask, TaskContext, TaskTemplate, TaskVariables};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
|
@ -146,100 +150,88 @@ fn active_item_selection_properties(
|
|||
}
|
||||
|
||||
fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
|
||||
let cwd = task_cwd(workspace, cx).log_err().flatten();
|
||||
let current_editor = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx));
|
||||
if let Some(current_editor) = current_editor {
|
||||
(|| {
|
||||
let editor = current_editor.read(cx);
|
||||
fn task_context_impl(workspace: &Workspace, cx: &mut WindowContext<'_>) -> Option<TaskContext> {
|
||||
let cwd = task_cwd(workspace, cx).log_err().flatten();
|
||||
let editor = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))?;
|
||||
|
||||
let (selection, buffer, editor_snapshot) = editor.update(cx, |editor, cx| {
|
||||
let selection = editor.selections.newest::<usize>(cx);
|
||||
let (buffer, _, _) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)?;
|
||||
let snapshot = editor.snapshot(cx);
|
||||
Some((selection, buffer, snapshot))
|
||||
})?;
|
||||
let language_context_provider = buffer
|
||||
.read(cx)
|
||||
.language()
|
||||
.and_then(|language| language.context_provider())?;
|
||||
|
||||
current_editor.update(cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let selection_range = selection.range();
|
||||
let start = snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let Point { row, column } = snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.offset_to_point(selection_range.start);
|
||||
let row = row + 1;
|
||||
let column = column + 1;
|
||||
let location = Location {
|
||||
buffer: buffer.clone(),
|
||||
range: start..end,
|
||||
};
|
||||
|
||||
let current_file = location
|
||||
.buffer
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.start)
|
||||
.text_anchor;
|
||||
let end = editor_snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.anchor_after(selection_range.end)
|
||||
.text_anchor;
|
||||
let worktree_abs_path = buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| WorktreeId::from_usize(file.worktree_id()))
|
||||
.and_then(|worktree_id| {
|
||||
workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.file()
|
||||
.and_then(|file| file.as_local())
|
||||
.map(|file| file.abs_path(cx).to_string_lossy().to_string());
|
||||
let worktree_id = location
|
||||
.buffer
|
||||
.read(cx)
|
||||
.file()
|
||||
.map(|file| WorktreeId::from_usize(file.worktree_id()));
|
||||
let context = buffer
|
||||
.read(cx)
|
||||
.language()
|
||||
.and_then(|language| language.context_provider())
|
||||
.and_then(|provider| provider.build_context(location, cx).ok());
|
||||
|
||||
let worktree_path = worktree_id.and_then(|worktree_id| {
|
||||
workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path().to_string_lossy().to_string())
|
||||
});
|
||||
|
||||
let selected_text = buffer.read(cx).chars_for_range(selection_range).collect();
|
||||
|
||||
let mut task_variables = TaskVariables::from_iter([
|
||||
(VariableName::Row, row.to_string()),
|
||||
(VariableName::Column, column.to_string()),
|
||||
(VariableName::SelectedText, selected_text),
|
||||
]);
|
||||
if let Some(path) = current_file {
|
||||
task_variables.insert(VariableName::File, path);
|
||||
}
|
||||
if let Some(worktree_path) = worktree_path {
|
||||
task_variables.insert(VariableName::WorktreeRoot, worktree_path);
|
||||
}
|
||||
if let Some(language_context) = context {
|
||||
task_variables.extend(language_context);
|
||||
}
|
||||
|
||||
Some(TaskContext {
|
||||
cwd: cwd.clone(),
|
||||
task_variables,
|
||||
})
|
||||
})
|
||||
})()
|
||||
.unwrap_or_else(|| TaskContext {
|
||||
.worktree_for_id(worktree_id, cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path())
|
||||
});
|
||||
let location = Location {
|
||||
buffer,
|
||||
range: start..end,
|
||||
};
|
||||
let task_variables = combine_task_variables(
|
||||
worktree_abs_path.as_deref(),
|
||||
location,
|
||||
language_context_provider.as_ref(),
|
||||
cx,
|
||||
)
|
||||
.log_err()?;
|
||||
Some(TaskContext {
|
||||
cwd,
|
||||
task_variables: Default::default(),
|
||||
task_variables,
|
||||
})
|
||||
}
|
||||
|
||||
task_context_impl(workspace, cx).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn combine_task_variables(
|
||||
worktree_abs_path: Option<&Path>,
|
||||
location: Location,
|
||||
context_provider: &dyn ContextProvider,
|
||||
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 {
|
||||
TaskContext {
|
||||
cwd,
|
||||
task_variables: Default::default(),
|
||||
}
|
||||
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)
|
||||
.context("building provider context ")?,
|
||||
);
|
||||
Ok(basic_context)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,7 +317,7 @@ mod tests {
|
|||
|
||||
use editor::Editor;
|
||||
use gpui::{Entity, TestAppContext};
|
||||
use language::{Language, LanguageConfig, SymbolContextProvider};
|
||||
use language::{BasicContextProvider, Language, LanguageConfig};
|
||||
use project::{FakeFs, Project};
|
||||
use serde_json::json;
|
||||
use task::{TaskContext, TaskVariables, VariableName};
|
||||
|
@ -375,7 +367,7 @@ mod tests {
|
|||
name: (_) @name) @item"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_context_provider(Some(Arc::new(SymbolContextProvider))),
|
||||
.with_context_provider(Some(Arc::new(BasicContextProvider))),
|
||||
);
|
||||
|
||||
let typescript_language = Arc::new(
|
||||
|
@ -393,7 +385,7 @@ mod tests {
|
|||
")" @context)) @item"#,
|
||||
)
|
||||
.unwrap()
|
||||
.with_context_provider(Some(Arc::new(SymbolContextProvider))),
|
||||
.with_context_provider(Some(Arc::new(BasicContextProvider))),
|
||||
);
|
||||
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||
let worktree_id = project.update(cx, |project, cx| {
|
||||
|
@ -435,7 +427,6 @@ mod tests {
|
|||
(VariableName::WorktreeRoot, "/dir".into()),
|
||||
(VariableName::Row, "1".into()),
|
||||
(VariableName::Column, "1".into()),
|
||||
(VariableName::SelectedText, "".into())
|
||||
])
|
||||
}
|
||||
);
|
||||
|
@ -469,7 +460,6 @@ mod tests {
|
|||
(VariableName::WorktreeRoot, "/dir".into()),
|
||||
(VariableName::Row, "1".into()),
|
||||
(VariableName::Column, "1".into()),
|
||||
(VariableName::SelectedText, "".into()),
|
||||
(VariableName::Symbol, "this_is_a_test".into()),
|
||||
])
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue