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