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}