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