active_command.rs

  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}