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