cargo_workspace_command.rs

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