active_command.rs

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