Rank exact extension ID matches higher in search results (#14588)

Conrad Irwin and Marshall created

Release Notes:

- Improved relevance of extension search results

Co-authored-by: Marshall <marshall@zed.dev>

Change summary

crates/collab/src/api/extensions.rs       | 21 +++++++++++++++++++--
crates/extensions_ui/src/extensions_ui.rs | 21 +++++++++++++++++++++
crates/rpc/src/extension.rs               |  4 ++--
3 files changed, 42 insertions(+), 4 deletions(-)

Detailed changes

crates/collab/src/api/extensions.rs 🔗

@@ -10,7 +10,7 @@ use axum::{
     Extension, Json, Router,
 };
 use collections::HashMap;
-use rpc::{ExtensionApiManifest, GetExtensionsResponse};
+use rpc::{ExtensionApiManifest, ExtensionMetadata, GetExtensionsResponse};
 use semantic_version::SemanticVersion;
 use serde::Deserialize;
 use std::{sync::Arc, time::Duration};
@@ -43,7 +43,7 @@ async fn get_extensions(
     Extension(app): Extension<Arc<AppState>>,
     Query(params): Query<GetExtensionsParams>,
 ) -> Result<Json<GetExtensionsResponse>> {
-    let extensions = app
+    let mut extensions = app
         .db
         .get_extensions(params.filter.as_deref(), params.max_schema_version, 500)
         .await?;
@@ -53,6 +53,23 @@ async fn get_extensions(
         tracing::info!(query, count, "extension_search")
     }
 
+    if let Some(filter) = params.filter.as_deref() {
+        let mut exact_match: Option<ExtensionMetadata> = None;
+        extensions.retain(|extension| {
+            exact_match = Some(extension.clone());
+            extension.id.as_ref() != &filter.to_lowercase()
+        });
+        if exact_match == None {
+            exact_match = app
+                .db
+                .get_extensions_by_ids(&[&filter.to_lowercase()], None)
+                .await?
+                .first()
+                .cloned();
+        }
+        extensions.splice(0..0, exact_match);
+    };
+
     Ok(Json(GetExtensionsResponse { data: extensions }))
 }
 

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -136,11 +136,14 @@ impl ExtensionFilter {
 enum Feature {
     Git,
     Vim,
+    LanguageBash,
     LanguageC,
     LanguageCpp,
     LanguageGo,
     LanguagePython,
+    LanguageReact,
     LanguageRust,
+    LanguageTypescript,
 }
 
 fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
@@ -149,11 +152,17 @@ fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
         BTreeMap::from_iter([
             (Feature::Git, vec!["git"]),
             (Feature::Vim, vec!["vim"]),
+            (Feature::LanguageBash, vec!["sh", "bash"]),
             (Feature::LanguageC, vec!["c", "clang"]),
             (Feature::LanguageCpp, vec!["c++", "cpp", "clang"]),
             (Feature::LanguageGo, vec!["go", "golang"]),
             (Feature::LanguagePython, vec!["python", "py"]),
+            (Feature::LanguageReact, vec!["react"]),
             (Feature::LanguageRust, vec!["rust", "rs"]),
+            (
+                Feature::LanguageTypescript,
+                vec!["type", "typescript", "ts"],
+            ),
         ])
     })
 }
@@ -973,6 +982,10 @@ impl ExtensionsPage {
                             );
                         }),
                     )),
+                Feature::LanguageBash => {
+                    FeatureUpsell::new(telemetry, "Shell support is built-in to Zed!")
+                        .docs_url("https://zed.dev/docs/languages/bash")
+                }
                 Feature::LanguageC => {
                     FeatureUpsell::new(telemetry, "C support is built-in to Zed!")
                         .docs_url("https://zed.dev/docs/languages/c")
@@ -989,10 +1002,18 @@ impl ExtensionsPage {
                     FeatureUpsell::new(telemetry, "Python support is built-in to Zed!")
                         .docs_url("https://zed.dev/docs/languages/python")
                 }
+                Feature::LanguageReact => {
+                    FeatureUpsell::new(telemetry, "React support is built-in to Zed!")
+                        .docs_url("https://zed.dev/docs/languages/typescript")
+                }
                 Feature::LanguageRust => {
                     FeatureUpsell::new(telemetry, "Rust support is built-in to Zed!")
                         .docs_url("https://zed.dev/docs/languages/rust")
                 }
+                Feature::LanguageTypescript => {
+                    FeatureUpsell::new(telemetry, "Typescript support is built-in to Zed!")
+                        .docs_url("https://zed.dev/docs/languages/typescript")
+                }
             };
 
             upsell.when(ix < upsells_count, |upsell| upsell.border_b_1())

crates/rpc/src/extension.rs 🔗

@@ -2,7 +2,7 @@ use chrono::{DateTime, Utc};
 use serde::{Deserialize, Serialize};
 use std::sync::Arc;
 
-#[derive(Serialize, Deserialize, Debug, PartialEq)]
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
 pub struct ExtensionApiManifest {
     pub name: String,
     pub version: Arc<str>,
@@ -13,7 +13,7 @@ pub struct ExtensionApiManifest {
     pub wasm_api_version: Option<String>,
 }
 
-#[derive(Serialize, Deserialize, Debug, PartialEq)]
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
 pub struct ExtensionMetadata {
     pub id: Arc<str>,
     #[serde(flatten)]