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}