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 content from open tabs".into()
21 }
22
23 fn tooltip_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 _cx: &mut AppContext,
36 ) -> Task<Result<Vec<String>>> {
37 Task::ready(Err(anyhow!("this command does not require argument")))
38 }
39
40 fn run(
41 self: Arc<Self>,
42 _argument: Option<&str>,
43 workspace: WeakView<Workspace>,
44 _delegate: Arc<dyn LspAdapterDelegate>,
45 cx: &mut WindowContext,
46 ) -> Task<Result<SlashCommandOutput>> {
47 let open_buffers = workspace.update(cx, |workspace, cx| {
48 let mut timestamps_by_entity_id = HashMap::default();
49 let mut open_buffers = Vec::new();
50
51 for pane in workspace.panes() {
52 let pane = pane.read(cx);
53 for entry in pane.activation_history() {
54 timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp);
55 }
56 }
57
58 for editor in workspace.items_of_type::<Editor>(cx) {
59 if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() {
60 if let Some(timestamp) = timestamps_by_entity_id.get(&editor.entity_id()) {
61 let snapshot = buffer.read(cx).snapshot();
62 let full_path = snapshot.resolve_file_path(cx, true);
63 open_buffers.push((full_path, snapshot, *timestamp));
64 }
65 }
66 }
67
68 open_buffers
69 });
70
71 match open_buffers {
72 Ok(mut open_buffers) => cx.background_executor().spawn(async move {
73 open_buffers.sort_by_key(|(_, _, timestamp)| *timestamp);
74
75 let mut sections = Vec::new();
76 let mut text = String::new();
77 for (full_path, buffer, _) in open_buffers {
78 let section_start_ix = text.len();
79 writeln!(
80 text,
81 "```{}\n",
82 full_path
83 .as_deref()
84 .unwrap_or(Path::new("untitled"))
85 .display()
86 )
87 .unwrap();
88 for chunk in buffer.as_rope().chunks() {
89 text.push_str(chunk);
90 }
91 if !text.ends_with('\n') {
92 text.push('\n');
93 }
94 writeln!(text, "```\n").unwrap();
95 let section_end_ix = text.len() - 1;
96
97 sections.push(SlashCommandOutputSection {
98 range: section_start_ix..section_end_ix,
99 render_placeholder: Arc::new(move |id, unfold, _| {
100 FilePlaceholder {
101 id,
102 path: full_path.clone(),
103 line_range: None,
104 unfold,
105 }
106 .into_any_element()
107 }),
108 });
109 }
110
111 Ok(SlashCommandOutput { text, sections })
112 }),
113 Err(error) => Task::ready(Err(error)),
114 }
115 }
116}