1use anyhow::{Context as _, 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
74 .outline(None)
75 .context("no symbols for active tab")?;
76
77 let path = path.as_deref().unwrap_or(Path::new("untitled"));
78 let mut outline_text = format!("Symbols for {}:\n", path.display());
79 for item in &outline.path_candidates {
80 outline_text.push_str("- ");
81 outline_text.push_str(&item.string);
82 outline_text.push('\n');
83 }
84
85 Ok(SlashCommandOutput {
86 sections: vec![SlashCommandOutputSection {
87 range: 0..outline_text.len(),
88 icon: IconName::ListTree,
89 label: path.to_string_lossy().to_string().into(),
90 metadata: None,
91 }],
92 text: outline_text,
93 run_commands_in_text: false,
94 }
95 .into_event_stream())
96 })
97 });
98
99 output.unwrap_or_else(|error| Task::ready(Err(error)))
100 }
101}