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 menu_text(&self) -> String {
26        self.description()
27    }
28
29    fn complete_argument(
30        self: Arc<Self>,
31        _arguments: &[String],
32        _cancel: Arc<AtomicBool>,
33        _workspace: Option<WeakView<Workspace>>,
34        _cx: &mut WindowContext,
35    ) -> Task<Result<Vec<ArgumentCompletion>>> {
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        _arguments: &[String],
46        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
47        _context_buffer: BufferSnapshot,
48        workspace: WeakView<Workspace>,
49        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
50        cx: &mut WindowContext,
51    ) -> Task<SlashCommandResult> {
52        let output = workspace.update(cx, |workspace, cx| {
53            let Some(active_item) = workspace.active_item(cx) else {
54                return Task::ready(Err(anyhow!("no active tab")));
55            };
56            let Some(buffer) = active_item
57                .downcast::<Editor>()
58                .and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton())
59            else {
60                return Task::ready(Err(anyhow!("active tab is not an editor")));
61            };
62
63            let snapshot = buffer.read(cx).snapshot();
64            let path = snapshot.resolve_file_path(cx, true);
65
66            cx.background_executor().spawn(async move {
67                let outline = snapshot
68                    .outline(None)
69                    .context("no symbols for active tab")?;
70
71                let path = path.as_deref().unwrap_or(Path::new("untitled"));
72                let mut outline_text = format!("Symbols for {}:\n", path.display());
73                for item in &outline.path_candidates {
74                    outline_text.push_str("- ");
75                    outline_text.push_str(&item.string);
76                    outline_text.push('\n');
77                }
78
79                Ok(SlashCommandOutput {
80                    sections: vec![SlashCommandOutputSection {
81                        range: 0..outline_text.len(),
82                        icon: IconName::ListTree,
83                        label: path.to_string_lossy().to_string().into(),
84                        metadata: None,
85                    }],
86                    text: outline_text,
87                    run_commands_in_text: false,
88                }
89                .to_event_stream())
90            })
91        });
92
93        output.unwrap_or_else(|error| Task::ready(Err(error)))
94    }
95}