cargo_workspace_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::{BufferSnapshot, 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 CargoWorkspaceSlashCommand;
 17
 18impl CargoWorkspaceSlashCommand {
 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 CargoWorkspaceSlashCommand {
 92    fn name(&self) -> String {
 93        "cargo-workspace".into()
 94    }
 95
 96    fn description(&self) -> String {
 97        "insert project workspace metadata".into()
 98    }
 99
100    fn menu_text(&self) -> String {
101        "Insert Project Workspace 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        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
122        _context_buffer: BufferSnapshot,
123        workspace: WeakView<Workspace>,
124        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
125        cx: &mut WindowContext,
126    ) -> Task<Result<SlashCommandOutput>> {
127        let output = workspace.update(cx, |workspace, cx| {
128            let project = workspace.project().clone();
129            let fs = workspace.project().read(cx).fs().clone();
130            let path = Self::path_to_cargo_toml(project, cx);
131            let output = cx.background_executor().spawn(async move {
132                let path = path.with_context(|| "Cargo.toml not found")?;
133                Self::build_message(fs, &path).await
134            });
135
136            cx.foreground_executor().spawn(async move {
137                let text = output.await?;
138                let range = 0..text.len();
139                Ok(SlashCommandOutput {
140                    text,
141                    sections: vec![SlashCommandOutputSection {
142                        range,
143                        icon: IconName::FileTree,
144                        label: "Project".into(),
145                        metadata: None,
146                    }],
147                    run_commands_in_text: false,
148                })
149            })
150        });
151        output.unwrap_or_else(|error| Task::ready(Err(error)))
152    }
153}