1use ::settings::Settings;
2use editor::{tasks::task_context, Editor};
3use gpui::{AppContext, Task as AsyncTask, ViewContext, WindowContext};
4use modal::TasksModal;
5use project::{Location, WorktreeId};
6use workspace::tasks::schedule_task;
7use workspace::{tasks::schedule_resolved_task, Workspace};
8
9mod modal;
10mod settings;
11
12pub use modal::Spawn;
13
14pub fn init(cx: &mut AppContext) {
15 settings::TaskSettings::register(cx);
16 cx.observe_new_views(
17 |workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
18 workspace
19 .register_action(spawn_task_or_modal)
20 .register_action(move |workspace, action: &modal::Rerun, cx| {
21 if let Some((task_source_kind, mut last_scheduled_task)) =
22 workspace.project().update(cx, |project, cx| {
23 project.task_inventory().read(cx).last_scheduled_task()
24 })
25 {
26 if action.reevaluate_context {
27 let mut original_task = last_scheduled_task.original_task().clone();
28 if let Some(allow_concurrent_runs) = action.allow_concurrent_runs {
29 original_task.allow_concurrent_runs = allow_concurrent_runs;
30 }
31 if let Some(use_new_terminal) = action.use_new_terminal {
32 original_task.use_new_terminal = use_new_terminal;
33 }
34 let context_task = task_context(workspace, cx);
35 cx.spawn(|workspace, mut cx| async move {
36 let task_context = context_task.await;
37 workspace
38 .update(&mut cx, |workspace, cx| {
39 schedule_task(
40 workspace,
41 task_source_kind,
42 &original_task,
43 &task_context,
44 false,
45 cx,
46 )
47 })
48 .ok()
49 })
50 .detach()
51 } else {
52 if let Some(resolved) = last_scheduled_task.resolved.as_mut() {
53 if let Some(allow_concurrent_runs) = action.allow_concurrent_runs {
54 resolved.allow_concurrent_runs = allow_concurrent_runs;
55 }
56 if let Some(use_new_terminal) = action.use_new_terminal {
57 resolved.use_new_terminal = use_new_terminal;
58 }
59 }
60
61 schedule_resolved_task(
62 workspace,
63 task_source_kind,
64 last_scheduled_task,
65 false,
66 cx,
67 );
68 }
69 } else {
70 toggle_modal(workspace, cx).detach();
71 };
72 });
73 },
74 )
75 .detach();
76}
77
78fn spawn_task_or_modal(workspace: &mut Workspace, action: &Spawn, cx: &mut ViewContext<Workspace>) {
79 match &action.task_name {
80 Some(name) => spawn_task_with_name(name.clone(), cx).detach_and_log_err(cx),
81 None => toggle_modal(workspace, cx).detach(),
82 }
83}
84
85fn toggle_modal(workspace: &mut Workspace, cx: &mut ViewContext<'_, Workspace>) -> AsyncTask<()> {
86 let project = workspace.project().clone();
87 let workspace_handle = workspace.weak_handle();
88 let context_task = task_context(workspace, cx);
89 cx.spawn(|workspace, mut cx| async move {
90 let task_context = context_task.await;
91 workspace
92 .update(&mut cx, |workspace, cx| {
93 if workspace.project().update(cx, |project, cx| {
94 project.is_local() || project.ssh_connection_string(cx).is_some()
95 }) {
96 workspace.toggle_modal(cx, |cx| {
97 TasksModal::new(project, task_context, workspace_handle, cx)
98 })
99 }
100 })
101 .ok();
102 })
103}
104
105fn spawn_task_with_name(
106 name: String,
107 cx: &mut ViewContext<Workspace>,
108) -> AsyncTask<anyhow::Result<()>> {
109 cx.spawn(|workspace, mut cx| async move {
110 let context_task =
111 workspace.update(&mut cx, |workspace, cx| task_context(workspace, cx))?;
112 let task_context = context_task.await;
113 let tasks = workspace
114 .update(&mut cx, |workspace, cx| {
115 let (worktree, location) = active_item_selection_properties(workspace, cx);
116 workspace.project().update(cx, |project, cx| {
117 project.task_templates(worktree, location, cx)
118 })
119 })?
120 .await?;
121
122 let did_spawn = workspace
123 .update(&mut cx, |workspace, cx| {
124 let (task_source_kind, target_task) =
125 tasks.into_iter().find(|(_, task)| task.label == name)?;
126 schedule_task(
127 workspace,
128 task_source_kind,
129 &target_task,
130 &task_context,
131 false,
132 cx,
133 );
134 Some(())
135 })?
136 .is_some();
137 if !did_spawn {
138 workspace
139 .update(&mut cx, |workspace, cx| {
140 spawn_task_or_modal(workspace, &Spawn::default(), cx);
141 })
142 .ok();
143 }
144
145 Ok(())
146 })
147}
148
149fn active_item_selection_properties(
150 workspace: &Workspace,
151 cx: &mut WindowContext,
152) -> (Option<WorktreeId>, Option<Location>) {
153 let active_item = workspace.active_item(cx);
154 let worktree_id = active_item
155 .as_ref()
156 .and_then(|item| item.project_path(cx))
157 .map(|path| path.worktree_id);
158 let location = active_item
159 .and_then(|active_item| active_item.act_as::<Editor>(cx))
160 .and_then(|editor| {
161 editor.update(cx, |editor, cx| {
162 let selection = editor.selections.newest_anchor();
163 let multi_buffer = editor.buffer().clone();
164 let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
165 let (buffer_snapshot, buffer_offset) =
166 multi_buffer_snapshot.point_to_buffer_offset(selection.head())?;
167 let buffer_anchor = buffer_snapshot.anchor_before(buffer_offset);
168 let buffer = multi_buffer.read(cx).buffer(buffer_snapshot.remote_id())?;
169 Some(Location {
170 buffer,
171 range: buffer_anchor..buffer_anchor,
172 })
173 })
174 });
175 (worktree_id, location)
176}
177
178#[cfg(test)]
179mod tests {
180 use std::sync::Arc;
181
182 use editor::Editor;
183 use gpui::{Entity, TestAppContext};
184 use language::{Language, LanguageConfig};
185 use project::{BasicContextProvider, FakeFs, Project};
186 use serde_json::json;
187 use task::{TaskContext, TaskVariables, VariableName};
188 use ui::VisualContext;
189 use workspace::{AppState, Workspace};
190
191 use crate::task_context;
192
193 #[gpui::test]
194 async fn test_default_language_context(cx: &mut TestAppContext) {
195 init_test(cx);
196 let fs = FakeFs::new(cx.executor());
197 fs.insert_tree(
198 "/dir",
199 json!({
200 ".zed": {
201 "tasks.json": r#"[
202 {
203 "label": "example task",
204 "command": "echo",
205 "args": ["4"]
206 },
207 {
208 "label": "another one",
209 "command": "echo",
210 "args": ["55"]
211 },
212 ]"#,
213 },
214 "a.ts": "function this_is_a_test() { }",
215 "rust": {
216 "b.rs": "use std; fn this_is_a_rust_file() { }",
217 }
218
219 }),
220 )
221 .await;
222 let project = Project::test(fs, ["/dir".as_ref()], cx).await;
223 let rust_language = Arc::new(
224 Language::new(
225 LanguageConfig::default(),
226 Some(tree_sitter_rust::language()),
227 )
228 .with_outline_query(
229 r#"(function_item
230 "fn" @context
231 name: (_) @name) @item"#,
232 )
233 .unwrap()
234 .with_context_provider(Some(Arc::new(BasicContextProvider::new(project.clone())))),
235 );
236
237 let typescript_language = Arc::new(
238 Language::new(
239 LanguageConfig::default(),
240 Some(tree_sitter_typescript::language_typescript()),
241 )
242 .with_outline_query(
243 r#"(function_declaration
244 "async"? @context
245 "function" @context
246 name: (_) @name
247 parameters: (formal_parameters
248 "(" @context
249 ")" @context)) @item"#,
250 )
251 .unwrap()
252 .with_context_provider(Some(Arc::new(BasicContextProvider::new(project.clone())))),
253 );
254
255 let worktree_id = project.update(cx, |project, cx| {
256 project.worktrees().next().unwrap().read(cx).id()
257 });
258 let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
259
260 let buffer1 = workspace
261 .update(cx, |this, cx| {
262 this.project()
263 .update(cx, |this, cx| this.open_buffer((worktree_id, "a.ts"), cx))
264 })
265 .await
266 .unwrap();
267 buffer1.update(cx, |this, cx| {
268 this.set_language(Some(typescript_language), cx)
269 });
270 let editor1 = cx.new_view(|cx| Editor::for_buffer(buffer1, Some(project.clone()), cx));
271 let buffer2 = workspace
272 .update(cx, |this, cx| {
273 this.project().update(cx, |this, cx| {
274 this.open_buffer((worktree_id, "rust/b.rs"), cx)
275 })
276 })
277 .await
278 .unwrap();
279 buffer2.update(cx, |this, cx| this.set_language(Some(rust_language), cx));
280 let editor2 = cx.new_view(|cx| Editor::for_buffer(buffer2, Some(project), cx));
281
282 let first_context = workspace
283 .update(cx, |workspace, cx| {
284 workspace.add_item_to_center(Box::new(editor1.clone()), cx);
285 workspace.add_item_to_center(Box::new(editor2.clone()), cx);
286 assert_eq!(
287 workspace.active_item(cx).unwrap().item_id(),
288 editor2.entity_id()
289 );
290 task_context(workspace, cx)
291 })
292 .await;
293 assert_eq!(
294 first_context,
295 TaskContext {
296 cwd: Some("/dir".into()),
297 task_variables: TaskVariables::from_iter([
298 (VariableName::File, "/dir/rust/b.rs".into()),
299 (VariableName::Filename, "b.rs".into()),
300 (VariableName::RelativeFile, "rust/b.rs".into()),
301 (VariableName::Dirname, "/dir/rust".into()),
302 (VariableName::Stem, "b".into()),
303 (VariableName::WorktreeRoot, "/dir".into()),
304 (VariableName::Row, "1".into()),
305 (VariableName::Column, "1".into()),
306 ])
307 }
308 );
309
310 // And now, let's select an identifier.
311 editor2.update(cx, |editor, cx| {
312 editor.change_selections(None, cx, |selections| selections.select_ranges([14..18]))
313 });
314
315 assert_eq!(
316 workspace
317 .update(cx, |workspace, cx| { task_context(workspace, cx) })
318 .await,
319 TaskContext {
320 cwd: Some("/dir".into()),
321 task_variables: TaskVariables::from_iter([
322 (VariableName::File, "/dir/rust/b.rs".into()),
323 (VariableName::Filename, "b.rs".into()),
324 (VariableName::RelativeFile, "rust/b.rs".into()),
325 (VariableName::Dirname, "/dir/rust".into()),
326 (VariableName::Stem, "b".into()),
327 (VariableName::WorktreeRoot, "/dir".into()),
328 (VariableName::Row, "1".into()),
329 (VariableName::Column, "15".into()),
330 (VariableName::SelectedText, "is_i".into()),
331 (VariableName::Symbol, "this_is_a_rust_file".into()),
332 ])
333 }
334 );
335
336 assert_eq!(
337 workspace
338 .update(cx, |workspace, cx| {
339 // Now, let's switch the active item to .ts file.
340 workspace.activate_item(&editor1, cx);
341 task_context(workspace, cx)
342 })
343 .await,
344 TaskContext {
345 cwd: Some("/dir".into()),
346 task_variables: TaskVariables::from_iter([
347 (VariableName::File, "/dir/a.ts".into()),
348 (VariableName::Filename, "a.ts".into()),
349 (VariableName::RelativeFile, "a.ts".into()),
350 (VariableName::Dirname, "/dir".into()),
351 (VariableName::Stem, "a".into()),
352 (VariableName::WorktreeRoot, "/dir".into()),
353 (VariableName::Row, "1".into()),
354 (VariableName::Column, "1".into()),
355 (VariableName::Symbol, "this_is_a_test".into()),
356 ])
357 }
358 );
359 }
360
361 pub(crate) fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
362 cx.update(|cx| {
363 let state = AppState::test(cx);
364 file_icons::init((), cx);
365 language::init(cx);
366 crate::init(cx);
367 editor::init(cx);
368 workspace::init_settings(cx);
369 Project::init_settings(cx);
370 state
371 })
372 }
373}