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::{borrow::Cow, sync::Arc};
9use ui::{IntoElement, WindowContext};
10use workspace::Workspace;
11
12pub(crate) struct ActiveSlashCommand;
13
14impl SlashCommand for ActiveSlashCommand {
15 fn name(&self) -> String {
16 "active".into()
17 }
18
19 fn description(&self) -> String {
20 "insert active tab".into()
21 }
22
23 fn tooltip_text(&self) -> String {
24 "insert active tab".into()
25 }
26
27 fn complete_argument(
28 &self,
29 _query: String,
30 _cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
31 _cx: &mut AppContext,
32 ) -> Task<Result<Vec<String>>> {
33 Task::ready(Err(anyhow!("this command does not require argument")))
34 }
35
36 fn requires_argument(&self) -> bool {
37 false
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 output = workspace.update(cx, |workspace, cx| {
48 let mut timestamps_by_entity_id = HashMap::default();
49 for pane in workspace.panes() {
50 let pane = pane.read(cx);
51 for entry in pane.activation_history() {
52 timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp);
53 }
54 }
55
56 let mut most_recent_buffer = None;
57 for editor in workspace.items_of_type::<Editor>(cx) {
58 let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() else {
59 continue;
60 };
61
62 let timestamp = timestamps_by_entity_id
63 .get(&editor.entity_id())
64 .copied()
65 .unwrap_or_default();
66 if most_recent_buffer
67 .as_ref()
68 .map_or(true, |(_, prev_timestamp)| timestamp > *prev_timestamp)
69 {
70 most_recent_buffer = Some((buffer, timestamp));
71 }
72 }
73
74 if let Some((buffer, _)) = most_recent_buffer {
75 let snapshot = buffer.read(cx).snapshot();
76 let path = snapshot.resolve_file_path(cx, true);
77 let text = cx.background_executor().spawn({
78 let path = path.clone();
79 async move {
80 let path = path
81 .as_ref()
82 .map(|path| path.to_string_lossy())
83 .unwrap_or_else(|| Cow::Borrowed("untitled"));
84
85 let mut output = String::with_capacity(path.len() + snapshot.len() + 9);
86 output.push_str("```");
87 output.push_str(&path);
88 output.push('\n');
89 for chunk in snapshot.as_rope().chunks() {
90 output.push_str(chunk);
91 }
92 if !output.ends_with('\n') {
93 output.push('\n');
94 }
95 output.push_str("```");
96 output
97 }
98 });
99 cx.foreground_executor().spawn(async move {
100 let text = text.await;
101 let range = 0..text.len();
102 Ok(SlashCommandOutput {
103 text,
104 sections: vec![SlashCommandOutputSection {
105 range,
106 render_placeholder: Arc::new(move |id, unfold, _| {
107 FilePlaceholder {
108 id,
109 path: path.clone(),
110 line_range: None,
111 unfold,
112 }
113 .into_any_element()
114 }),
115 }],
116 })
117 })
118 } else {
119 Task::ready(Err(anyhow!("no recent buffer found")))
120 }
121 });
122 output.unwrap_or_else(|error| Task::ready(Err(error)))
123 }
124}