symbols_command.rs

 1use anyhow::{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.outline(None);
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                .into_event_stream())
94            })
95        });
96
97        output.unwrap_or_else(|error| Task::ready(Err(error)))
98    }
99}