symbols_command.rs

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