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}