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 collections::HashMap;
8use editor::Editor;
9use gpui::{AppContext, Entity, Task, WeakView};
10use language::LspAdapterDelegate;
11use std::{fmt::Write, sync::Arc};
12use ui::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: Arc<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 let mut has_diagnostics = false;
82 for (full_path, buffer, _) in open_buffers {
83 let section_start_ix = text.len();
84 text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
85 for chunk in buffer.as_rope().chunks() {
86 text.push_str(chunk);
87 }
88 if !text.ends_with('\n') {
89 text.push('\n');
90 }
91 writeln!(text, "```").unwrap();
92 if write_single_file_diagnostics(&mut text, full_path.as_deref(), &buffer) {
93 has_diagnostics = true;
94 }
95 if !text.ends_with('\n') {
96 text.push('\n');
97 }
98
99 let section_end_ix = text.len() - 1;
100 sections.push(build_entry_output_section(
101 section_start_ix..section_end_ix,
102 full_path.as_deref(),
103 false,
104 None,
105 ));
106 }
107
108 Ok(SlashCommandOutput {
109 text,
110 sections,
111 run_commands_in_text: has_diagnostics,
112 })
113 }),
114 Err(error) => Task::ready(Err(error)),
115 }
116 }
117}