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