1use super::{
2 diagnostics_command::write_single_file_diagnostics,
3 file_command::{build_entry_output_section, codeblock_fence_for_path},
4 SlashCommand, SlashCommandOutput,
5};
6use anyhow::{anyhow, Result};
7use assistant_slash_command::ArgumentCompletion;
8use collections::HashMap;
9use editor::Editor;
10use gpui::{AppContext, Entity, Task, WeakView};
11use language::LspAdapterDelegate;
12use std::{fmt::Write, sync::Arc};
13use ui::WindowContext;
14use workspace::Workspace;
15
16pub(crate) struct TabsSlashCommand;
17
18impl SlashCommand for TabsSlashCommand {
19 fn name(&self) -> String {
20 "tabs".into()
21 }
22
23 fn description(&self) -> String {
24 "insert open tabs".into()
25 }
26
27 fn menu_text(&self) -> String {
28 "Insert Open Tabs".into()
29 }
30
31 fn requires_argument(&self) -> bool {
32 false
33 }
34
35 fn complete_argument(
36 self: Arc<Self>,
37 _query: String,
38 _cancel: Arc<std::sync::atomic::AtomicBool>,
39 _workspace: Option<WeakView<Workspace>>,
40 _cx: &mut AppContext,
41 ) -> Task<Result<Vec<ArgumentCompletion>>> {
42 Task::ready(Err(anyhow!("this command does not require argument")))
43 }
44
45 fn run(
46 self: Arc<Self>,
47 _argument: Option<&str>,
48 workspace: WeakView<Workspace>,
49 _delegate: Option<Arc<dyn LspAdapterDelegate>>,
50 cx: &mut WindowContext,
51 ) -> Task<Result<SlashCommandOutput>> {
52 let open_buffers = workspace.update(cx, |workspace, cx| {
53 let mut timestamps_by_entity_id = HashMap::default();
54 let mut open_buffers = Vec::new();
55
56 for pane in workspace.panes() {
57 let pane = pane.read(cx);
58 for entry in pane.activation_history() {
59 timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp);
60 }
61 }
62
63 for editor in workspace.items_of_type::<Editor>(cx) {
64 if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() {
65 if let Some(timestamp) = timestamps_by_entity_id.get(&editor.entity_id()) {
66 let snapshot = buffer.read(cx).snapshot();
67 let full_path = snapshot.resolve_file_path(cx, true);
68 open_buffers.push((full_path, snapshot, *timestamp));
69 }
70 }
71 }
72
73 open_buffers
74 });
75
76 match open_buffers {
77 Ok(mut open_buffers) => cx.background_executor().spawn(async move {
78 open_buffers.sort_by_key(|(_, _, timestamp)| *timestamp);
79
80 let mut sections = Vec::new();
81 let mut text = String::new();
82 let mut has_diagnostics = false;
83 for (full_path, buffer, _) in open_buffers {
84 let section_start_ix = text.len();
85 text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
86 for chunk in buffer.as_rope().chunks() {
87 text.push_str(chunk);
88 }
89 if !text.ends_with('\n') {
90 text.push('\n');
91 }
92 writeln!(text, "```").unwrap();
93 if write_single_file_diagnostics(&mut text, full_path.as_deref(), &buffer) {
94 has_diagnostics = true;
95 }
96 if !text.ends_with('\n') {
97 text.push('\n');
98 }
99
100 let section_end_ix = text.len() - 1;
101 sections.push(build_entry_output_section(
102 section_start_ix..section_end_ix,
103 full_path.as_deref(),
104 false,
105 None,
106 ));
107 }
108
109 Ok(SlashCommandOutput {
110 text,
111 sections,
112 run_commands_in_text: has_diagnostics,
113 })
114 }),
115 Err(error) => Task::ready(Err(error)),
116 }
117 }
118}