1use super::{SlashCommand, SlashCommandOutput};
2use anyhow::{anyhow, Context, Result};
3use fs::Fs;
4use gpui::{AppContext, Model, Task, WeakView};
5use language::LspAdapterDelegate;
6use project::{Project, ProjectPath};
7use std::{
8 fmt::Write,
9 path::Path,
10 sync::{atomic::AtomicBool, Arc},
11};
12use ui::{prelude::*, ButtonLike, ElevationIndex};
13use workspace::Workspace;
14
15pub(crate) struct ProjectSlashCommand;
16
17impl ProjectSlashCommand {
18 async fn build_message(fs: Arc<dyn Fs>, path_to_cargo_toml: &Path) -> Result<String> {
19 let buffer = fs.load(path_to_cargo_toml).await?;
20 let cargo_toml: cargo_toml::Manifest = toml::from_str(&buffer)?;
21
22 let mut message = String::new();
23 writeln!(message, "You are in a Rust project.")?;
24
25 if let Some(workspace) = cargo_toml.workspace {
26 writeln!(
27 message,
28 "The project is a Cargo workspace with the following members:"
29 )?;
30 for member in workspace.members {
31 writeln!(message, "- {member}")?;
32 }
33
34 if !workspace.default_members.is_empty() {
35 writeln!(message, "The default members are:")?;
36 for member in workspace.default_members {
37 writeln!(message, "- {member}")?;
38 }
39 }
40
41 if !workspace.dependencies.is_empty() {
42 writeln!(
43 message,
44 "The following workspace dependencies are installed:"
45 )?;
46 for dependency in workspace.dependencies.keys() {
47 writeln!(message, "- {dependency}")?;
48 }
49 }
50 } else if let Some(package) = cargo_toml.package {
51 writeln!(
52 message,
53 "The project name is \"{name}\".",
54 name = package.name
55 )?;
56
57 let description = package
58 .description
59 .as_ref()
60 .and_then(|description| description.get().ok().cloned());
61 if let Some(description) = description.as_ref() {
62 writeln!(message, "It describes itself as \"{description}\".")?;
63 }
64
65 if !cargo_toml.dependencies.is_empty() {
66 writeln!(message, "The following dependencies are installed:")?;
67 for dependency in cargo_toml.dependencies.keys() {
68 writeln!(message, "- {dependency}")?;
69 }
70 }
71 }
72
73 Ok(message)
74 }
75
76 fn path_to_cargo_toml(project: Model<Project>, cx: &mut AppContext) -> Option<Arc<Path>> {
77 let worktree = project.read(cx).worktrees().next()?;
78 let worktree = worktree.read(cx);
79 let entry = worktree.entry_for_path("Cargo.toml")?;
80 let path = ProjectPath {
81 worktree_id: worktree.id(),
82 path: entry.path.clone(),
83 };
84 Some(Arc::from(
85 project.read(cx).absolute_path(&path, cx)?.as_path(),
86 ))
87 }
88}
89
90impl SlashCommand for ProjectSlashCommand {
91 fn name(&self) -> String {
92 "project".into()
93 }
94
95 fn description(&self) -> String {
96 "insert current project context".into()
97 }
98
99 fn tooltip_text(&self) -> String {
100 "insert current project context".into()
101 }
102
103 fn complete_argument(
104 &self,
105 _query: String,
106 _cancel: Arc<AtomicBool>,
107 _cx: &mut AppContext,
108 ) -> Task<Result<Vec<String>>> {
109 Task::ready(Err(anyhow!("this command does not require argument")))
110 }
111
112 fn requires_argument(&self) -> bool {
113 false
114 }
115
116 fn run(
117 self: Arc<Self>,
118 _argument: Option<&str>,
119 workspace: WeakView<Workspace>,
120 _delegate: Arc<dyn LspAdapterDelegate>,
121 cx: &mut WindowContext,
122 ) -> Task<Result<SlashCommandOutput>> {
123 let output = workspace.update(cx, |workspace, cx| {
124 let project = workspace.project().clone();
125 let fs = workspace.project().read(cx).fs().clone();
126 let path = Self::path_to_cargo_toml(project, cx);
127 let output = cx.background_executor().spawn(async move {
128 let path = path.with_context(|| "Cargo.toml not found")?;
129 Self::build_message(fs, &path).await
130 });
131
132 cx.foreground_executor().spawn(async move {
133 let text = output.await?;
134
135 Ok(SlashCommandOutput {
136 text,
137 render_placeholder: Arc::new(move |id, unfold, _cx| {
138 ButtonLike::new(id)
139 .style(ButtonStyle::Filled)
140 .layer(ElevationIndex::ElevatedSurface)
141 .child(Icon::new(IconName::FileTree))
142 .child(Label::new("Project"))
143 .on_click(move |_, cx| unfold(cx))
144 .into_any_element()
145 }),
146 })
147 })
148 });
149 output.unwrap_or_else(|error| Task::ready(Err(error)))
150 }
151}