active_command.rs

  1use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
  2use anyhow::{anyhow, Result};
  3use assistant_slash_command::SlashCommandOutputSection;
  4use editor::Editor;
  5use gpui::{AppContext, Task, WeakView};
  6use language::LspAdapterDelegate;
  7use std::{borrow::Cow, sync::Arc};
  8use ui::{IntoElement, WindowContext};
  9use workspace::Workspace;
 10
 11pub(crate) struct ActiveSlashCommand;
 12
 13impl SlashCommand for ActiveSlashCommand {
 14    fn name(&self) -> String {
 15        "active".into()
 16    }
 17
 18    fn description(&self) -> String {
 19        "insert active tab".into()
 20    }
 21
 22    fn menu_text(&self) -> String {
 23        "Insert Active Tab".into()
 24    }
 25
 26    fn complete_argument(
 27        &self,
 28        _query: String,
 29        _cancel: std::sync::Arc<std::sync::atomic::AtomicBool>,
 30        _workspace: WeakView<Workspace>,
 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 Some(active_item) = workspace.active_item(cx) else {
 49                return Task::ready(Err(anyhow!("no active tab")));
 50            };
 51            let Some(buffer) = active_item
 52                .downcast::<Editor>()
 53                .and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton())
 54            else {
 55                return Task::ready(Err(anyhow!("active tab is not an editor")));
 56            };
 57
 58            let snapshot = buffer.read(cx).snapshot();
 59            let path = snapshot.resolve_file_path(cx, true);
 60            let text = cx.background_executor().spawn({
 61                let path = path.clone();
 62                async move {
 63                    let path = path
 64                        .as_ref()
 65                        .map(|path| path.to_string_lossy())
 66                        .unwrap_or_else(|| Cow::Borrowed("untitled"));
 67
 68                    let mut output = String::with_capacity(path.len() + snapshot.len() + 9);
 69                    output.push_str("```");
 70                    output.push_str(&path);
 71                    output.push('\n');
 72                    for chunk in snapshot.as_rope().chunks() {
 73                        output.push_str(chunk);
 74                    }
 75                    if !output.ends_with('\n') {
 76                        output.push('\n');
 77                    }
 78                    output.push_str("```");
 79                    output
 80                }
 81            });
 82            cx.foreground_executor().spawn(async move {
 83                let text = text.await;
 84                let range = 0..text.len();
 85                Ok(SlashCommandOutput {
 86                    text,
 87                    sections: vec![SlashCommandOutputSection {
 88                        range,
 89                        render_placeholder: Arc::new(move |id, unfold, _| {
 90                            FilePlaceholder {
 91                                id,
 92                                path: path.clone(),
 93                                line_range: None,
 94                                unfold,
 95                            }
 96                            .into_any_element()
 97                        }),
 98                    }],
 99                    run_commands_in_text: false,
100                })
101            })
102        });
103        output.unwrap_or_else(|error| Task::ready(Err(error)))
104    }
105}