Allow clients to run Zed tasks on remote projects (#12199)
Release Notes: - Enabled Zed tasks on remote projects with ssh connection string specified --------- Co-authored-by: Conrad Irwin <conrad@zed.dev>
This commit is contained in:
co-authored by
Conrad Irwin
parent
df35fd0026
commit
055a13a9b6
+107
-86
@@ -4011,28 +4011,29 @@ impl Editor {
|
||||
let deployed_from_indicator = action.deployed_from_indicator;
|
||||
let mut task = self.code_actions_task.take();
|
||||
let action = action.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn(|editor, mut cx| async move {
|
||||
while let Some(prev_task) = task {
|
||||
prev_task.await;
|
||||
task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
|
||||
task = editor.update(&mut cx, |this, _| this.code_actions_task.take())?;
|
||||
}
|
||||
|
||||
let spawned_test_task = this.update(&mut cx, |this, cx| {
|
||||
if this.focus_handle.is_focused(cx) {
|
||||
let spawned_test_task = editor.update(&mut cx, |editor, cx| {
|
||||
if editor.focus_handle.is_focused(cx) {
|
||||
let multibuffer_point = action
|
||||
.deployed_from_indicator
|
||||
.map(|row| DisplayPoint::new(row, 0).to_point(&snapshot))
|
||||
.unwrap_or_else(|| this.selections.newest::<Point>(cx).head());
|
||||
.unwrap_or_else(|| editor.selections.newest::<Point>(cx).head());
|
||||
let (buffer, buffer_row) = snapshot
|
||||
.buffer_snapshot
|
||||
.buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
|
||||
.and_then(|(buffer_snapshot, range)| {
|
||||
this.buffer
|
||||
editor
|
||||
.buffer
|
||||
.read(cx)
|
||||
.buffer(buffer_snapshot.remote_id())
|
||||
.map(|buffer| (buffer, range.start.row))
|
||||
})?;
|
||||
let (_, code_actions) = this
|
||||
let (_, code_actions) = editor
|
||||
.available_code_actions
|
||||
.clone()
|
||||
.and_then(|(location, code_actions)| {
|
||||
@@ -4047,7 +4048,7 @@ impl Editor {
|
||||
})
|
||||
.unzip();
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let tasks = this
|
||||
let tasks = editor
|
||||
.tasks
|
||||
.get(&(buffer_id, buffer_row))
|
||||
.map(|t| Arc::new(t.to_owned()));
|
||||
@@ -4055,81 +4056,100 @@ impl Editor {
|
||||
return None;
|
||||
}
|
||||
|
||||
this.completion_tasks.clear();
|
||||
this.discard_inline_completion(false, cx);
|
||||
let tasks = tasks.as_ref().zip(this.workspace.clone()).and_then(
|
||||
|(tasks, (workspace, _))| {
|
||||
let position = Point::new(buffer_row, tasks.column);
|
||||
let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
|
||||
let location = Location {
|
||||
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.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(
|
||||
editor.completion_tasks.clear();
|
||||
editor.discard_inline_completion(false, cx);
|
||||
let task_context =
|
||||
tasks
|
||||
.as_ref()
|
||||
.zip(editor.project.clone())
|
||||
.map(|(tasks, project)| {
|
||||
let position = Point::new(buffer_row, tasks.column);
|
||||
let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
|
||||
let location = Location {
|
||||
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.extra_variables.clone() {
|
||||
captured_task_variables.insert(
|
||||
task::VariableName::Custom(capture_name.into()),
|
||||
value.clone(),
|
||||
);
|
||||
}
|
||||
project.update(cx, |project, cx| {
|
||||
project.task_context_for_location(
|
||||
captured_task_variables,
|
||||
workspace,
|
||||
location,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|task_context| {
|
||||
Arc::new(ResolvedTasks {
|
||||
templates: tasks
|
||||
.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.column),
|
||||
),
|
||||
})
|
||||
});
|
||||
|
||||
Some(cx.spawn(|editor, mut cx| async move {
|
||||
let task_context = match task_context {
|
||||
Some(task_context) => task_context.await,
|
||||
None => None,
|
||||
};
|
||||
let resolved_tasks =
|
||||
tasks.zip(task_context).map(|(tasks, task_context)| {
|
||||
Arc::new(ResolvedTasks {
|
||||
templates: tasks
|
||||
.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.column,
|
||||
)),
|
||||
})
|
||||
},
|
||||
);
|
||||
let spawn_straight_away = tasks
|
||||
.as_ref()
|
||||
.map_or(false, |tasks| tasks.templates.len() == 1)
|
||||
&& code_actions
|
||||
});
|
||||
let spawn_straight_away = resolved_tasks
|
||||
.as_ref()
|
||||
.map_or(true, |actions| actions.is_empty());
|
||||
*this.context_menu.write() = Some(ContextMenu::CodeActions(CodeActionsMenu {
|
||||
buffer,
|
||||
actions: CodeActionContents {
|
||||
tasks,
|
||||
actions: code_actions,
|
||||
},
|
||||
selected_item: Default::default(),
|
||||
scroll_handle: UniformListScrollHandle::default(),
|
||||
deployed_from_indicator,
|
||||
}));
|
||||
if spawn_straight_away {
|
||||
if let Some(task) =
|
||||
this.confirm_code_action(&ConfirmCodeAction { item_ix: Some(0) }, cx)
|
||||
.map_or(false, |tasks| tasks.templates.len() == 1)
|
||||
&& code_actions
|
||||
.as_ref()
|
||||
.map_or(true, |actions| actions.is_empty());
|
||||
if let Some(task) = editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
*editor.context_menu.write() =
|
||||
Some(ContextMenu::CodeActions(CodeActionsMenu {
|
||||
buffer,
|
||||
actions: CodeActionContents {
|
||||
tasks: resolved_tasks,
|
||||
actions: code_actions,
|
||||
},
|
||||
selected_item: Default::default(),
|
||||
scroll_handle: UniformListScrollHandle::default(),
|
||||
deployed_from_indicator,
|
||||
}));
|
||||
if spawn_straight_away {
|
||||
if let Some(task) = editor.confirm_code_action(
|
||||
&ConfirmCodeAction { item_ix: Some(0) },
|
||||
cx,
|
||||
) {
|
||||
cx.notify();
|
||||
return task;
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
Task::ready(Ok(()))
|
||||
})
|
||||
.ok()
|
||||
{
|
||||
cx.notify();
|
||||
return Some(task);
|
||||
task.await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
}))
|
||||
} else {
|
||||
Some(Task::ready(Ok(())))
|
||||
}
|
||||
Some(Task::ready(Ok(())))
|
||||
})?;
|
||||
if let Some(task) = spawned_test_task {
|
||||
task.await?;
|
||||
@@ -7897,11 +7917,14 @@ impl Editor {
|
||||
let Some(project) = project else {
|
||||
return;
|
||||
};
|
||||
if project
|
||||
.update(&mut cx, |this, _| this.is_remote())
|
||||
.unwrap_or(true)
|
||||
{
|
||||
// Do not display any test indicators in remote projects.
|
||||
|
||||
let hide_runnables = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
// Do not display any test indicators in non-dev server remote projects.
|
||||
project.is_remote() && project.ssh_connection_string(cx).is_none()
|
||||
})
|
||||
.unwrap_or(true);
|
||||
if hide_runnables {
|
||||
return;
|
||||
}
|
||||
let new_rows =
|
||||
@@ -7940,10 +7963,8 @@ impl Editor {
|
||||
runnable_ranges
|
||||
.into_iter()
|
||||
.filter_map(|mut runnable| {
|
||||
let (tasks, _) = cx
|
||||
.update(|cx| {
|
||||
Self::resolve_runnable(project.clone(), &mut runnable.runnable, cx)
|
||||
})
|
||||
let tasks = cx
|
||||
.update(|cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
|
||||
.ok()?;
|
||||
if tasks.is_empty() {
|
||||
return None;
|
||||
@@ -7974,11 +7995,11 @@ impl Editor {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn resolve_runnable(
|
||||
project: Model<Project>,
|
||||
fn templates_with_tags(
|
||||
project: &Model<Project>,
|
||||
runnable: &mut Runnable,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> (Vec<(TaskSourceKind, TaskTemplate)>, Option<WorktreeId>) {
|
||||
) -> Vec<(TaskSourceKind, TaskTemplate)> {
|
||||
let (inventory, worktree_id) = project.read_with(cx, |project, cx| {
|
||||
let worktree_id = project
|
||||
.buffer_for_id(runnable.buffer)
|
||||
@@ -8015,7 +8036,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
(tags, worktree_id)
|
||||
tags
|
||||
}
|
||||
|
||||
pub fn move_to_enclosing_bracket(
|
||||
|
||||
+23
-66
@@ -1,58 +1,34 @@
|
||||
use crate::Editor;
|
||||
|
||||
use anyhow::Context;
|
||||
use gpui::{Model, WindowContext};
|
||||
use language::ContextProvider;
|
||||
use project::{BasicContextProvider, Location, Project};
|
||||
use gpui::{Task as AsyncTask, WindowContext};
|
||||
use project::Location;
|
||||
use task::{TaskContext, TaskVariables, VariableName};
|
||||
use text::{Point, ToOffset, ToPoint};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) fn task_context_for_location(
|
||||
captured_variables: TaskVariables,
|
||||
workspace: &Workspace,
|
||||
location: Location,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> Option<TaskContext> {
|
||||
let cwd = workspace::tasks::task_cwd(workspace, cx)
|
||||
.log_err()
|
||||
.flatten();
|
||||
|
||||
let mut task_variables = combine_task_variables(
|
||||
captured_variables,
|
||||
location,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
fn task_context_with_editor(
|
||||
workspace: &Workspace,
|
||||
editor: &mut Editor,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> Option<TaskContext> {
|
||||
) -> AsyncTask<Option<TaskContext>> {
|
||||
let Some(project) = editor.project.clone() else {
|
||||
return AsyncTask::ready(None);
|
||||
};
|
||||
let (selection, buffer, editor_snapshot) = {
|
||||
let mut selection = editor.selections.newest::<Point>(cx);
|
||||
if editor.selections.line_mode {
|
||||
selection.start = Point::new(selection.start.row, 0);
|
||||
selection.end = Point::new(selection.end.row + 1, 0);
|
||||
}
|
||||
let (buffer, _, _) = editor
|
||||
let Some((buffer, _, _)) = editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(selection.start, cx)?;
|
||||
.point_to_buffer_offset(selection.start, cx)
|
||||
else {
|
||||
return AsyncTask::ready(None);
|
||||
};
|
||||
let snapshot = editor.snapshot(cx);
|
||||
Some((selection, buffer, snapshot))
|
||||
}?;
|
||||
(selection, buffer, snapshot)
|
||||
};
|
||||
let selection_range = selection.range();
|
||||
let start = editor_snapshot
|
||||
.display_snapshot
|
||||
@@ -94,42 +70,23 @@ fn task_context_with_editor(
|
||||
}
|
||||
variables
|
||||
};
|
||||
task_context_for_location(captured_variables, workspace, location.clone(), cx)
|
||||
|
||||
let context_task = project.update(cx, |project, cx| {
|
||||
project.task_context_for_location(captured_variables, location.clone(), cx)
|
||||
});
|
||||
cx.spawn(|_| context_task)
|
||||
}
|
||||
|
||||
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> TaskContext {
|
||||
pub fn task_context(workspace: &Workspace, cx: &mut WindowContext<'_>) -> AsyncTask<TaskContext> {
|
||||
let Some(editor) = workspace
|
||||
.active_item(cx)
|
||||
.and_then(|item| item.act_as::<Editor>(cx))
|
||||
else {
|
||||
return Default::default();
|
||||
return AsyncTask::ready(TaskContext::default());
|
||||
};
|
||||
editor.update(cx, |editor, cx| {
|
||||
task_context_with_editor(workspace, editor, cx).unwrap_or_default()
|
||||
let context_task = task_context_with_editor(editor, cx);
|
||||
cx.background_executor()
|
||||
.spawn(async move { context_task.await.unwrap_or_default() })
|
||||
})
|
||||
}
|
||||
|
||||
fn combine_task_variables(
|
||||
mut captured_variables: TaskVariables,
|
||||
location: Location,
|
||||
project: Model<Project>,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> anyhow::Result<TaskVariables> {
|
||||
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(captured_variables)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user