tabs_command.rs

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