symbols_command.rs

  1use anyhow::{Context as _, Result, anyhow};
  2use assistant_slash_command::{
  3    ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
  4    SlashCommandResult,
  5};
  6use editor::Editor;
  7use gpui::{AppContext as _, Task, WeakEntity};
  8use language::{BufferSnapshot, LspAdapterDelegate};
  9use std::sync::Arc;
 10use std::{path::Path, sync::atomic::AtomicBool};
 11use ui::{App, IconName, Window};
 12use workspace::Workspace;
 13
 14pub struct OutlineSlashCommand;
 15
 16impl SlashCommand for OutlineSlashCommand {
 17    fn name(&self) -> String {
 18        "symbols".into()
 19    }
 20
 21    fn description(&self) -> String {
 22        "Insert symbols for active tab".into()
 23    }
 24
 25    fn icon(&self) -> IconName {
 26        IconName::ListTree
 27    }
 28
 29    fn menu_text(&self) -> String {
 30        self.description()
 31    }
 32
 33    fn complete_argument(
 34        self: Arc<Self>,
 35        _arguments: &[String],
 36        _cancel: Arc<AtomicBool>,
 37        _workspace: Option<WeakEntity<Workspace>>,
 38        _window: &mut Window,
 39        _cx: &mut App,
 40    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 41        Task::ready(Err(anyhow!("this command does not require argument")))
 42    }
 43
 44    fn requires_argument(&self) -> bool {
 45        false
 46    }
 47
 48    fn run(
 49        self: Arc<Self>,
 50        _arguments: &[String],
 51        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
 52        _context_buffer: BufferSnapshot,
 53        workspace: WeakEntity<Workspace>,
 54        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
 55        _: &mut Window,
 56        cx: &mut App,
 57    ) -> Task<SlashCommandResult> {
 58        let output = workspace.update(cx, |workspace, cx| {
 59            let Some(active_item) = workspace.active_item(cx) else {
 60                return Task::ready(Err(anyhow!("no active tab")));
 61            };
 62            let Some(buffer) = active_item
 63                .downcast::<Editor>()
 64                .and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton())
 65            else {
 66                return Task::ready(Err(anyhow!("active tab is not an editor")));
 67            };
 68
 69            let snapshot = buffer.read(cx).snapshot();
 70            let path = snapshot.resolve_file_path(cx, true);
 71
 72            cx.background_spawn(async move {
 73                let outline = snapshot
 74                    .outline(None)
 75                    .context("no symbols for active tab")?;
 76
 77                let path = path.as_deref().unwrap_or(Path::new("untitled"));
 78                let mut outline_text = format!("Symbols for {}:\n", path.display());
 79                for item in &outline.path_candidates {
 80                    outline_text.push_str("- ");
 81                    outline_text.push_str(&item.string);
 82                    outline_text.push('\n');
 83                }
 84
 85                Ok(SlashCommandOutput {
 86                    sections: vec![SlashCommandOutputSection {
 87                        range: 0..outline_text.len(),
 88                        icon: IconName::ListTree,
 89                        label: path.to_string_lossy().to_string().into(),
 90                        metadata: None,
 91                    }],
 92                    text: outline_text,
 93                    run_commands_in_text: false,
 94                }
 95                .into_event_stream())
 96            })
 97        });
 98
 99        output.unwrap_or_else(|error| Task::ready(Err(error)))
100    }
101}