project_command.rs

  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        _arguments: &[String],
107        _cancel: Arc<AtomicBool>,
108        _workspace: Option<WeakView<Workspace>>,
109        _cx: &mut WindowContext,
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        _arguments: &[String],
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}