1use super::{SlashCommand, SlashCommandOutput};
2use anyhow::{anyhow, Context as _, Result};
3use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
4use editor::Editor;
5use gpui::{AppContext, Task, WeakView};
6use language::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 _query: String,
30 _cancel: Arc<AtomicBool>,
31 _workspace: Option<WeakView<Workspace>>,
32 _cx: &mut AppContext,
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 _argument: Option<&str>,
44 workspace: WeakView<Workspace>,
45 _delegate: Arc<dyn LspAdapterDelegate>,
46 cx: &mut WindowContext,
47 ) -> Task<Result<SlashCommandOutput>> {
48 let output = workspace.update(cx, |workspace, cx| {
49 let Some(active_item) = workspace.active_item(cx) else {
50 return Task::ready(Err(anyhow!("no active tab")));
51 };
52 let Some(buffer) = active_item
53 .downcast::<Editor>()
54 .and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton())
55 else {
56 return Task::ready(Err(anyhow!("active tab is not an editor")));
57 };
58
59 let snapshot = buffer.read(cx).snapshot();
60 let path = snapshot.resolve_file_path(cx, true);
61
62 cx.background_executor().spawn(async move {
63 let outline = snapshot
64 .outline(None)
65 .context("no symbols for active tab")?;
66
67 let path = path.as_deref().unwrap_or(Path::new("untitled"));
68 let mut outline_text = format!("Symbols for {}:\n", path.display());
69 for item in &outline.path_candidates {
70 outline_text.push_str("- ");
71 outline_text.push_str(&item.string);
72 outline_text.push('\n');
73 }
74
75 Ok(SlashCommandOutput {
76 sections: vec![SlashCommandOutputSection {
77 range: 0..outline_text.len(),
78 icon: IconName::ListTree,
79 label: path.to_string_lossy().to_string().into(),
80 }],
81 text: outline_text,
82 run_commands_in_text: false,
83 })
84 })
85 });
86
87 output.unwrap_or_else(|error| Task::ready(Err(error)))
88 }
89}