task: Add ZED_PACKAGE task variable in Rust files. (#9491)

Piotr Osiewicz and Kirill created

This variable is experimental, as I expect it to be superseded by
whatever the extensions can provide (once we get them)

Release Notes:

- Added experimental ZED_PACKAGE task variable which contains name of
the current crate in Rust files.

---------

Co-authored-by: Kirill <kirill@zed.dev>

Change summary

crates/languages/src/lib.rs  | 10 +++++++-
crates/languages/src/rust.rs | 42 +++++++++++++++++++++++++++++++++++++
crates/tasks_ui/src/lib.rs   |  3 ++
3 files changed, 53 insertions(+), 2 deletions(-)

Detailed changes

crates/languages/src/lib.rs 🔗

@@ -7,6 +7,8 @@ use settings::Settings;
 use std::{str, sync::Arc};
 use util::asset_str;
 
+use crate::rust::RustContextProvider;
+
 use self::{deno::DenoSettings, elixir::ElixirSettings};
 
 mod astro;
@@ -150,7 +152,7 @@ pub fn init(
             let config = load_config($name);
             // typeck helper
             let adapters: Vec<Arc<dyn LspAdapter>> = $adapters;
-            for adapter in $adapters {
+            for adapter in adapters {
                 languages.register_lsp_adapter(config.name.clone(), adapter);
             }
             languages.register_language(
@@ -240,7 +242,11 @@ pub fn init(
             node_runtime.clone(),
         ))]
     );
-    language!("rust", vec![Arc::new(rust::RustLspAdapter)]);
+    language!(
+        "rust",
+        vec![Arc::new(rust::RustLspAdapter)],
+        RustContextProvider
+    );
     language!("toml", vec![Arc::new(toml::TaploLspAdapter)]);
     match &DenoSettings::get(None, cx).enable {
         true => {

crates/languages/src/rust.rs 🔗

@@ -317,6 +317,48 @@ impl LspAdapter for RustLspAdapter {
     }
 }
 
+pub(crate) struct RustContextProvider;
+
+impl LanguageContextProvider for RustContextProvider {
+    fn build_context(
+        &self,
+        location: Location,
+        cx: &mut gpui::AppContext,
+    ) -> Result<LanguageContext> {
+        let mut context = DefaultContextProvider.build_context(location.clone(), cx)?;
+        if context.package.is_none() {
+            if let Some(path) = location.buffer.read(cx).file().and_then(|file| {
+                let local_file = file.as_local()?.abs_path(cx);
+                local_file.parent().map(PathBuf::from)
+            }) {
+                // src/
+                //  main.rs
+                //  lib.rs
+                //  foo/
+                //      bar/
+                //          baz.rs <|>
+                //  /bin/
+                //     bin_1.rs
+                //
+                let Some(pkgid) = std::process::Command::new("cargo")
+                    .current_dir(path)
+                    .arg("pkgid")
+                    .output()
+                    .log_err()
+                else {
+                    return Ok(context);
+                };
+                let package_name = String::from_utf8(pkgid.stdout)
+                    .map(|name| name.trim().to_owned())
+                    .ok();
+
+                context.package = package_name;
+            }
+        }
+        Ok(context)
+    }
+}
+
 async fn get_cached_server_binary(container_dir: PathBuf) -> Option<LanguageServerBinary> {
     async_maybe!({
         let mut last = None;

crates/tasks_ui/src/lib.rs 🔗

@@ -171,6 +171,9 @@ fn task_context(
                     if let Some(symbol) = language_context.symbol {
                         env.insert("ZED_SYMBOL".into(), symbol);
                     }
+                    if let Some(symbol) = language_context.package {
+                        env.insert("ZED_PACKAGE".into(), symbol);
+                    }
                 }
 
                 Some(TaskContext {