Compare commits
1 Commits
fix-git-ht
...
tasks-moda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d895f53337 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -10153,6 +10153,7 @@ dependencies = [
|
|||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"menu",
|
"menu",
|
||||||
|
"parking_lot",
|
||||||
"picker",
|
"picker",
|
||||||
"project",
|
"project",
|
||||||
"schemars",
|
"schemars",
|
||||||
@@ -10160,6 +10161,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"task",
|
"task",
|
||||||
|
"text",
|
||||||
"tree-sitter-rust",
|
"tree-sitter-rust",
|
||||||
"tree-sitter-typescript",
|
"tree-sitter-typescript",
|
||||||
"ui",
|
"ui",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use editor::{scroll::Autoscroll, Editor};
|
use editor::{scroll::Autoscroll, CompletionProvider, Editor};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, impl_actions, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent,
|
actions, div, impl_actions, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent,
|
||||||
DismissEvent, EventEmitter, FocusHandle, FocusableView, Length, ListState, MouseButton,
|
DismissEvent, EventEmitter, FocusHandle, FocusableView, Length, ListState, MouseButton,
|
||||||
@@ -166,6 +166,17 @@ impl<D: PickerDelegate> Picker<D> {
|
|||||||
Self::new(delegate, ContainerKind::List, head, cx)
|
Self::new(delegate, ContainerKind::List, head, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a completion provider for this pickers query editor, if it has one.
|
||||||
|
pub fn with_completions_provider(
|
||||||
|
self,
|
||||||
|
provider: Box<dyn CompletionProvider>,
|
||||||
|
cx: &mut WindowContext<'_>,
|
||||||
|
) -> Self {
|
||||||
|
if let Head::Editor(editor) = &self.head {
|
||||||
|
editor.update(cx, |this, _| this.set_completion_provider(provider))
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
fn new(delegate: D, container: ContainerKind, head: Head, cx: &mut ViewContext<Self>) -> Self {
|
fn new(delegate: D, container: ContainerKind, head: Head, cx: &mut ViewContext<Self>) -> Self {
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
delegate,
|
delegate,
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ pub mod static_source;
|
|||||||
mod task_template;
|
mod task_template;
|
||||||
mod vscode_format;
|
mod vscode_format;
|
||||||
|
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::{borrow::Cow, path::Path};
|
use std::{borrow::Cow, path::Path};
|
||||||
|
|
||||||
pub use task_template::{RevealStrategy, TaskTemplate, TaskTemplates};
|
pub use task_template::{RevealStrategy, TaskTemplate, TaskTemplates};
|
||||||
@@ -120,7 +121,7 @@ impl ResolvedTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Variables, available for use in [`TaskContext`] when a Zed's [`TaskTemplate`] gets resolved into a [`ResolvedTask`].
|
/// Variables, available for use in [`TaskContext`] when a Zed's [`TaskTemplate`] gets resolved into a [`ResolvedTask`].
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||||
pub enum VariableName {
|
pub enum VariableName {
|
||||||
/// An absolute path of the currently opened file.
|
/// An absolute path of the currently opened file.
|
||||||
File,
|
File,
|
||||||
@@ -134,8 +135,6 @@ pub enum VariableName {
|
|||||||
Column,
|
Column,
|
||||||
/// Text from the latest selection.
|
/// Text from the latest selection.
|
||||||
SelectedText,
|
SelectedText,
|
||||||
/// The symbol selected by the symbol tagging system, specifically the @run capture in a runnables.scm
|
|
||||||
RunnableSymbol,
|
|
||||||
/// Custom variable, provided by the plugin or other external source.
|
/// Custom variable, provided by the plugin or other external source.
|
||||||
/// Will be printed with `ZED_` prefix to avoid potential conflicts with other variables.
|
/// Will be printed with `ZED_` prefix to avoid potential conflicts with other variables.
|
||||||
Custom(Cow<'static, str>),
|
Custom(Cow<'static, str>),
|
||||||
@@ -165,7 +164,6 @@ impl std::fmt::Display for VariableName {
|
|||||||
Self::Row => write!(f, "{ZED_VARIABLE_NAME_PREFIX}ROW"),
|
Self::Row => write!(f, "{ZED_VARIABLE_NAME_PREFIX}ROW"),
|
||||||
Self::Column => write!(f, "{ZED_VARIABLE_NAME_PREFIX}COLUMN"),
|
Self::Column => write!(f, "{ZED_VARIABLE_NAME_PREFIX}COLUMN"),
|
||||||
Self::SelectedText => write!(f, "{ZED_VARIABLE_NAME_PREFIX}SELECTED_TEXT"),
|
Self::SelectedText => write!(f, "{ZED_VARIABLE_NAME_PREFIX}SELECTED_TEXT"),
|
||||||
Self::RunnableSymbol => write!(f, "{ZED_VARIABLE_NAME_PREFIX}RUNNABLE_SYMBOL"),
|
|
||||||
Self::Custom(s) => write!(f, "{ZED_VARIABLE_NAME_PREFIX}CUSTOM_{s}"),
|
Self::Custom(s) => write!(f, "{ZED_VARIABLE_NAME_PREFIX}CUSTOM_{s}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,7 +171,7 @@ impl std::fmt::Display for VariableName {
|
|||||||
|
|
||||||
/// Container for predefined environment variables that describe state of Zed at the time the task was spawned.
|
/// Container for predefined environment variables that describe state of Zed at the time the task was spawned.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
||||||
pub struct TaskVariables(HashMap<VariableName, String>);
|
pub struct TaskVariables(BTreeMap<VariableName, String>);
|
||||||
|
|
||||||
impl TaskVariables {
|
impl TaskVariables {
|
||||||
/// Inserts another variable into the container, overwriting the existing one if it already exists — in this case, the old value is returned.
|
/// Inserts another variable into the container, overwriting the existing one if it already exists — in this case, the old value is returned.
|
||||||
@@ -199,14 +197,42 @@ impl TaskVariables {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns iterator over names of all set task variables.
|
||||||
|
pub fn keys(&self) -> impl Iterator<Item = &VariableName> {
|
||||||
|
self.0.keys()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromIterator<(VariableName, String)> for TaskVariables {
|
impl FromIterator<(VariableName, String)> for TaskVariables {
|
||||||
fn from_iter<T: IntoIterator<Item = (VariableName, String)>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = (VariableName, String)>>(iter: T) -> Self {
|
||||||
Self(HashMap::from_iter(iter))
|
Self(BTreeMap::from_iter(iter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for VariableName {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let without_prefix = s.strip_prefix(ZED_VARIABLE_NAME_PREFIX).ok_or(())?;
|
||||||
|
let value = match without_prefix {
|
||||||
|
"FILE" => Self::File,
|
||||||
|
"WORKTREE_ROOT" => Self::WorktreeRoot,
|
||||||
|
"SYMBOL" => Self::Symbol,
|
||||||
|
"SELECTED_TEXT" => Self::SelectedText,
|
||||||
|
"ROW" => Self::Row,
|
||||||
|
"COLUMN" => Self::Column,
|
||||||
|
_ => {
|
||||||
|
if let Some(custom_name) = without_prefix.strip_prefix("CUSTOM_") {
|
||||||
|
Self::Custom(Cow::Owned(custom_name.to_owned()))
|
||||||
|
} else {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
/// Keeps track of the file associated with a task and context of tasks execution (i.e. current file or current function).
|
/// Keeps track of the file associated with a task and context of tasks execution (i.e. current file or current function).
|
||||||
/// Keeps all Zed-related state inside, used to produce a resolved task out of its template.
|
/// Keeps all Zed-related state inside, used to produce a resolved task out of its template.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ file_icons.workspace = true
|
|||||||
fuzzy.workspace = true
|
fuzzy.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
|
parking_lot.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
task.workspace = true
|
task.workspace = true
|
||||||
|
text.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use workspace::tasks::schedule_task;
|
|||||||
use workspace::{tasks::schedule_resolved_task, Workspace};
|
use workspace::{tasks::schedule_resolved_task, Workspace};
|
||||||
|
|
||||||
mod modal;
|
mod modal;
|
||||||
|
mod modal_completions;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
pub use modal::Spawn;
|
pub use modal::Spawn;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::active_item_selection_properties;
|
use crate::{active_item_selection_properties, modal_completions::TaskVariablesCompletionProvider};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusableView,
|
impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusableView,
|
||||||
@@ -139,11 +139,14 @@ impl TasksModal {
|
|||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let provider = TaskVariablesCompletionProvider::new(task_context.task_variables.clone());
|
||||||
|
|
||||||
let picker = cx.new_view(|cx| {
|
let picker = cx.new_view(|cx| {
|
||||||
Picker::uniform_list(
|
Picker::uniform_list(
|
||||||
TasksModalDelegate::new(inventory, task_context, workspace),
|
TasksModalDelegate::new(inventory, task_context, workspace),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
.with_completions_provider(Box::new(provider), cx)
|
||||||
});
|
});
|
||||||
let _subscription = cx.subscribe(&picker, |_, _, _, cx| {
|
let _subscription = cx.subscribe(&picker, |_, _, _, cx| {
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
|
|||||||
138
crates/tasks_ui/src/modal_completions.rs
Normal file
138
crates/tasks_ui/src/modal_completions.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
use std::{str::FromStr, sync::Arc};
|
||||||
|
|
||||||
|
use editor::CompletionProvider;
|
||||||
|
use fuzzy::{CharBag, StringMatchCandidate};
|
||||||
|
use gpui::{AppContext, Model, Task};
|
||||||
|
use language::{CodeLabel, Documentation, LanguageServerId};
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use task::{TaskVariables, VariableName};
|
||||||
|
use text::{Anchor, ToOffset};
|
||||||
|
use ui::ViewContext;
|
||||||
|
|
||||||
|
pub(crate) struct TaskVariablesCompletionProvider {
|
||||||
|
task_variables: Arc<TaskVariables>,
|
||||||
|
pub(crate) names: Arc<[StringMatchCandidate]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskVariablesCompletionProvider {
|
||||||
|
pub(crate) fn new(variables: TaskVariables) -> Self {
|
||||||
|
let names = variables
|
||||||
|
.keys()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, name)| {
|
||||||
|
let name = name.to_string();
|
||||||
|
StringMatchCandidate {
|
||||||
|
id: index,
|
||||||
|
char_bag: CharBag::from(name.as_str()),
|
||||||
|
string: name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Arc<[_]>>();
|
||||||
|
Self {
|
||||||
|
names,
|
||||||
|
task_variables: Arc::new(variables),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn current_query(
|
||||||
|
buffer: &Model<language::Buffer>,
|
||||||
|
position: language::Anchor,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Option<String> {
|
||||||
|
let mut has_trigger_character = false;
|
||||||
|
let reversed_query = buffer
|
||||||
|
.read(cx)
|
||||||
|
.reversed_chars_for_range(Anchor::MIN..position)
|
||||||
|
.take_while(|c| {
|
||||||
|
let is_trigger = *c == '$';
|
||||||
|
if is_trigger {
|
||||||
|
has_trigger_character = true;
|
||||||
|
}
|
||||||
|
!is_trigger && (*c == '_' || c.is_ascii_alphanumeric())
|
||||||
|
})
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
has_trigger_character.then(|| reversed_query.chars().rev().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompletionProvider for TaskVariablesCompletionProvider {
|
||||||
|
fn completions(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<language::Buffer>,
|
||||||
|
buffer_position: text::Anchor,
|
||||||
|
cx: &mut ViewContext<editor::Editor>,
|
||||||
|
) -> gpui::Task<gpui::Result<Vec<project::Completion>>> {
|
||||||
|
let Some(current_query) = Self::current_query(buffer, buffer_position, cx) else {
|
||||||
|
return Task::ready(Ok(vec![]));
|
||||||
|
};
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
let buffer_snapshot = buffer.snapshot();
|
||||||
|
let offset = buffer_position.to_offset(&buffer_snapshot);
|
||||||
|
let starting_offset = offset - current_query.len();
|
||||||
|
let starting_anchor = buffer.anchor_before(starting_offset);
|
||||||
|
let executor = cx.background_executor().clone();
|
||||||
|
let names = self.names.clone();
|
||||||
|
let variables = self.task_variables.clone();
|
||||||
|
cx.background_executor().spawn(async move {
|
||||||
|
let matches = fuzzy::match_strings(
|
||||||
|
&names,
|
||||||
|
¤t_query,
|
||||||
|
true,
|
||||||
|
100,
|
||||||
|
&Default::default(),
|
||||||
|
executor,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
// Find all variables starting with this
|
||||||
|
Ok(matches
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|hit| {
|
||||||
|
let variable_key = VariableName::from_str(&hit.string).ok()?;
|
||||||
|
let value_of_var = variables.get(&variable_key)?.to_owned();
|
||||||
|
Some(project::Completion {
|
||||||
|
old_range: starting_anchor..buffer_position,
|
||||||
|
new_text: hit.string.clone(),
|
||||||
|
label: CodeLabel::plain(hit.string, None),
|
||||||
|
documentation: Some(Documentation::SingleLine(value_of_var)),
|
||||||
|
server_id: LanguageServerId(0), // TODO: Make this optional or something?
|
||||||
|
lsp_completion: Default::default(), // TODO: Make this optional or something?
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_completions(
|
||||||
|
&self,
|
||||||
|
_buffer: Model<language::Buffer>,
|
||||||
|
_completion_indices: Vec<usize>,
|
||||||
|
_completions: Arc<RwLock<Box<[project::Completion]>>>,
|
||||||
|
_cx: &mut ViewContext<editor::Editor>,
|
||||||
|
) -> gpui::Task<gpui::Result<bool>> {
|
||||||
|
Task::ready(Ok(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_additional_edits_for_completion(
|
||||||
|
&self,
|
||||||
|
_buffer: Model<language::Buffer>,
|
||||||
|
_completion: project::Completion,
|
||||||
|
_push_to_history: bool,
|
||||||
|
_cx: &mut ViewContext<editor::Editor>,
|
||||||
|
) -> gpui::Task<gpui::Result<Option<language::Transaction>>> {
|
||||||
|
Task::ready(Ok(None))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_completion_trigger(
|
||||||
|
&self,
|
||||||
|
buffer: &Model<language::Buffer>,
|
||||||
|
position: language::Anchor,
|
||||||
|
text: &str,
|
||||||
|
_trigger_in_words: bool,
|
||||||
|
cx: &mut ViewContext<editor::Editor>,
|
||||||
|
) -> bool {
|
||||||
|
if text == "$" {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Self::current_query(buffer, position, cx).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user