1use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
2use anyhow::{anyhow, Result};
3use assistant_slash_command::{
4 ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
5};
6use collections::HashSet;
7use futures::future;
8use gpui::{Task, WeakView, WindowContext};
9use language::{BufferSnapshot, LspAdapterDelegate};
10use std::sync::{atomic::AtomicBool, Arc};
11use text::OffsetRangeExt;
12use workspace::Workspace;
13
14pub(crate) struct DeltaSlashCommand;
15
16impl SlashCommand for DeltaSlashCommand {
17 fn name(&self) -> String {
18 "delta".into()
19 }
20
21 fn description(&self) -> String {
22 "Re-insert changed files".into()
23 }
24
25 fn menu_text(&self) -> String {
26 self.description()
27 }
28
29 fn requires_argument(&self) -> bool {
30 false
31 }
32
33 fn complete_argument(
34 self: Arc<Self>,
35 _arguments: &[String],
36 _cancellation_flag: Arc<AtomicBool>,
37 _workspace: Option<WeakView<Workspace>>,
38 _cx: &mut WindowContext,
39 ) -> Task<Result<Vec<ArgumentCompletion>>> {
40 Task::ready(Err(anyhow!("this command does not require argument")))
41 }
42
43 fn run(
44 self: Arc<Self>,
45 _arguments: &[String],
46 context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
47 context_buffer: BufferSnapshot,
48 workspace: WeakView<Workspace>,
49 delegate: Option<Arc<dyn LspAdapterDelegate>>,
50 cx: &mut WindowContext,
51 ) -> Task<Result<SlashCommandOutput>> {
52 let mut paths = HashSet::default();
53 let mut file_command_old_outputs = Vec::new();
54 let mut file_command_new_outputs = Vec::new();
55 for section in context_slash_command_output_sections.iter().rev() {
56 if let Some(metadata) = section
57 .metadata
58 .as_ref()
59 .and_then(|value| serde_json::from_value::<FileCommandMetadata>(value.clone()).ok())
60 {
61 if paths.insert(metadata.path.clone()) {
62 file_command_old_outputs.push(
63 context_buffer
64 .as_rope()
65 .slice(section.range.to_offset(&context_buffer)),
66 );
67 file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
68 &[metadata.path.clone()],
69 context_slash_command_output_sections,
70 context_buffer.clone(),
71 workspace.clone(),
72 delegate.clone(),
73 cx,
74 ));
75 }
76 }
77 }
78
79 cx.background_executor().spawn(async move {
80 let mut output = SlashCommandOutput::default();
81
82 let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
83 for (old_text, new_output) in file_command_old_outputs
84 .into_iter()
85 .zip(file_command_new_outputs)
86 {
87 if let Ok(new_output) = new_output {
88 if let Some(file_command_range) = new_output.sections.first() {
89 let new_text = &new_output.text[file_command_range.range.clone()];
90 if old_text.chars().ne(new_text.chars()) {
91 output.sections.extend(new_output.sections.into_iter().map(
92 |section| SlashCommandOutputSection {
93 range: output.text.len() + section.range.start
94 ..output.text.len() + section.range.end,
95 icon: section.icon,
96 label: section.label,
97 metadata: section.metadata,
98 },
99 ));
100 output.text.push_str(&new_output.text);
101 }
102 }
103 }
104 }
105
106 Ok(output)
107 })
108 }
109}