tabs_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::{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}