Compare commits
1 Commits
v0.138.1-p
...
tasks-moda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d895f53337 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -10153,6 +10153,7 @@ dependencies = [
|
||||
"gpui",
|
||||
"language",
|
||||
"menu",
|
||||
"parking_lot",
|
||||
"picker",
|
||||
"project",
|
||||
"schemars",
|
||||
@@ -10160,6 +10161,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"settings",
|
||||
"task",
|
||||
"text",
|
||||
"tree-sitter-rust",
|
||||
"tree-sitter-typescript",
|
||||
"ui",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use editor::{scroll::Autoscroll, Editor};
|
||||
use editor::{scroll::Autoscroll, CompletionProvider, Editor};
|
||||
use gpui::{
|
||||
actions, div, impl_actions, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent,
|
||||
DismissEvent, EventEmitter, FocusHandle, FocusableView, Length, ListState, MouseButton,
|
||||
@@ -166,6 +166,17 @@ impl<D: PickerDelegate> Picker<D> {
|
||||
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 {
|
||||
let mut this = Self {
|
||||
delegate,
|
||||
|
||||
@@ -5,10 +5,11 @@ pub mod static_source;
|
||||
mod task_template;
|
||||
mod vscode_format;
|
||||
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use gpui::SharedString;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::{borrow::Cow, path::Path};
|
||||
|
||||
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`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub enum VariableName {
|
||||
/// An absolute path of the currently opened file.
|
||||
File,
|
||||
@@ -134,8 +135,6 @@ pub enum VariableName {
|
||||
Column,
|
||||
/// Text from the latest selection.
|
||||
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.
|
||||
/// Will be printed with `ZED_` prefix to avoid potential conflicts with other variables.
|
||||
Custom(Cow<'static, str>),
|
||||
@@ -165,7 +164,6 @@ impl std::fmt::Display for VariableName {
|
||||
Self::Row => write!(f, "{ZED_VARIABLE_NAME_PREFIX}ROW"),
|
||||
Self::Column => write!(f, "{ZED_VARIABLE_NAME_PREFIX}COLUMN"),
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
||||
pub struct TaskVariables(HashMap<VariableName, String>);
|
||||
pub struct TaskVariables(BTreeMap<VariableName, String>);
|
||||
|
||||
impl TaskVariables {
|
||||
/// 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 {
|
||||
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 all Zed-related state inside, used to produce a resolved task out of its template.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
||||
|
||||
@@ -14,9 +14,11 @@ file_icons.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
menu.workspace = true
|
||||
parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
task.workspace = true
|
||||
text.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
|
||||
@@ -10,6 +10,7 @@ use workspace::tasks::schedule_task;
|
||||
use workspace::{tasks::schedule_resolved_task, Workspace};
|
||||
|
||||
mod modal;
|
||||
mod modal_completions;
|
||||
mod settings;
|
||||
|
||||
pub use modal::Spawn;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::active_item_selection_properties;
|
||||
use crate::{active_item_selection_properties, modal_completions::TaskVariablesCompletionProvider};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter, FocusableView,
|
||||
@@ -139,11 +139,14 @@ impl TasksModal {
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let provider = TaskVariablesCompletionProvider::new(task_context.task_variables.clone());
|
||||
|
||||
let picker = cx.new_view(|cx| {
|
||||
Picker::uniform_list(
|
||||
TasksModalDelegate::new(inventory, task_context, workspace),
|
||||
cx,
|
||||
)
|
||||
.with_completions_provider(Box::new(provider), cx)
|
||||
});
|
||||
let _subscription = cx.subscribe(&picker, |_, _, _, cx| {
|
||||
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