Add zed://extension/{id} links (#34492)
Release Notes: - Add zed://extension/{id} links to open the extensions UI with a specific extension
This commit is contained in:
parent
ec52e9281a
commit
3751737621
9 changed files with 72 additions and 10 deletions
|
@ -491,6 +491,7 @@ impl AgentConfiguration {
|
||||||
category_filter: Some(
|
category_filter: Some(
|
||||||
ExtensionCategoryFilter::ContextServers,
|
ExtensionCategoryFilter::ContextServers,
|
||||||
),
|
),
|
||||||
|
id: None,
|
||||||
}
|
}
|
||||||
.boxed_clone(),
|
.boxed_clone(),
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -1921,6 +1921,7 @@ impl AgentPanel {
|
||||||
category_filter: Some(
|
category_filter: Some(
|
||||||
zed_actions::ExtensionCategoryFilter::ContextServers,
|
zed_actions::ExtensionCategoryFilter::ContextServers,
|
||||||
),
|
),
|
||||||
|
id: None,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.action("Add Custom Server…", Box::new(AddContextServer))
|
.action("Add Custom Server…", Box::new(AddContextServer))
|
||||||
|
|
|
@ -1760,6 +1760,7 @@ impl Render for DebugPanel {
|
||||||
category_filter: Some(
|
category_filter: Some(
|
||||||
zed_actions::ExtensionCategoryFilter::DebugAdapters,
|
zed_actions::ExtensionCategoryFilter::DebugAdapters,
|
||||||
),
|
),
|
||||||
|
id: None,
|
||||||
}
|
}
|
||||||
.boxed_clone(),
|
.boxed_clone(),
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::sync::OnceLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
use client::{ExtensionMetadata, ExtensionProvides};
|
use client::{ExtensionMetadata, ExtensionProvides};
|
||||||
use collections::{BTreeMap, BTreeSet};
|
use collections::{BTreeMap, BTreeSet};
|
||||||
use editor::{Editor, EditorElement, EditorStyle};
|
use editor::{Editor, EditorElement, EditorStyle};
|
||||||
|
@ -80,16 +81,24 @@ pub fn init(cx: &mut App) {
|
||||||
.find_map(|item| item.downcast::<ExtensionsPage>());
|
.find_map(|item| item.downcast::<ExtensionsPage>());
|
||||||
|
|
||||||
if let Some(existing) = existing {
|
if let Some(existing) = existing {
|
||||||
if provides_filter.is_some() {
|
existing.update(cx, |extensions_page, cx| {
|
||||||
existing.update(cx, |extensions_page, cx| {
|
if provides_filter.is_some() {
|
||||||
extensions_page.change_provides_filter(provides_filter, cx);
|
extensions_page.change_provides_filter(provides_filter, cx);
|
||||||
});
|
}
|
||||||
}
|
if let Some(id) = action.id.as_ref() {
|
||||||
|
extensions_page.focus_extension(id, window, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
workspace.activate_item(&existing, true, true, window, cx);
|
workspace.activate_item(&existing, true, true, window, cx);
|
||||||
} else {
|
} else {
|
||||||
let extensions_page =
|
let extensions_page = ExtensionsPage::new(
|
||||||
ExtensionsPage::new(workspace, provides_filter, window, cx);
|
workspace,
|
||||||
|
provides_filter,
|
||||||
|
action.id.as_deref(),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
workspace.add_item_to_active_pane(
|
workspace.add_item_to_active_pane(
|
||||||
Box::new(extensions_page),
|
Box::new(extensions_page),
|
||||||
None,
|
None,
|
||||||
|
@ -287,6 +296,7 @@ impl ExtensionsPage {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
provides_filter: Option<ExtensionProvides>,
|
provides_filter: Option<ExtensionProvides>,
|
||||||
|
focus_extension_id: Option<&str>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
|
@ -317,6 +327,9 @@ impl ExtensionsPage {
|
||||||
let query_editor = cx.new(|cx| {
|
let query_editor = cx.new(|cx| {
|
||||||
let mut input = Editor::single_line(window, cx);
|
let mut input = Editor::single_line(window, cx);
|
||||||
input.set_placeholder_text("Search extensions...", cx);
|
input.set_placeholder_text("Search extensions...", cx);
|
||||||
|
if let Some(id) = focus_extension_id {
|
||||||
|
input.set_text(format!("id:{id}"), window, cx);
|
||||||
|
}
|
||||||
input
|
input
|
||||||
});
|
});
|
||||||
cx.subscribe(&query_editor, Self::on_query_change).detach();
|
cx.subscribe(&query_editor, Self::on_query_change).detach();
|
||||||
|
@ -340,7 +353,7 @@ impl ExtensionsPage {
|
||||||
scrollbar_state: ScrollbarState::new(scroll_handle),
|
scrollbar_state: ScrollbarState::new(scroll_handle),
|
||||||
};
|
};
|
||||||
this.fetch_extensions(
|
this.fetch_extensions(
|
||||||
None,
|
this.search_query(cx),
|
||||||
Some(BTreeSet::from_iter(this.provides_filter)),
|
Some(BTreeSet::from_iter(this.provides_filter)),
|
||||||
None,
|
None,
|
||||||
cx,
|
cx,
|
||||||
|
@ -464,9 +477,23 @@ impl ExtensionsPage {
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let remote_extensions = extension_store.update(cx, |store, cx| {
|
let remote_extensions =
|
||||||
store.fetch_extensions(search.as_deref(), provides_filter.as_ref(), cx)
|
if let Some(id) = search.as_ref().and_then(|s| s.strip_prefix("id:")) {
|
||||||
});
|
let versions =
|
||||||
|
extension_store.update(cx, |store, cx| store.fetch_extension_versions(id, cx));
|
||||||
|
cx.foreground_executor().spawn(async move {
|
||||||
|
let versions = versions.await?;
|
||||||
|
let latest = versions
|
||||||
|
.into_iter()
|
||||||
|
.max_by_key(|v| v.published_at)
|
||||||
|
.context("no extension found")?;
|
||||||
|
Ok(vec![latest])
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
extension_store.update(cx, |store, cx| {
|
||||||
|
store.fetch_extensions(search.as_deref(), provides_filter.as_ref(), cx)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let dev_extensions = if let Some(search) = search {
|
let dev_extensions = if let Some(search) = search {
|
||||||
|
@ -1165,6 +1192,13 @@ impl ExtensionsPage {
|
||||||
self.refresh_feature_upsells(cx);
|
self.refresh_feature_upsells(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn focus_extension(&mut self, id: &str, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
self.query_editor.update(cx, |editor, cx| {
|
||||||
|
editor.set_text(format!("id:{id}"), window, cx)
|
||||||
|
});
|
||||||
|
self.refresh_search(cx);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn change_provides_filter(
|
pub fn change_provides_filter(
|
||||||
&mut self,
|
&mut self,
|
||||||
provides_filter: Option<ExtensionProvides>,
|
provides_filter: Option<ExtensionProvides>,
|
||||||
|
|
|
@ -327,6 +327,7 @@ impl PickerDelegate for IconThemeSelectorDelegate {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
Box::new(Extensions {
|
Box::new(Extensions {
|
||||||
category_filter: Some(ExtensionCategoryFilter::IconThemes),
|
category_filter: Some(ExtensionCategoryFilter::IconThemes),
|
||||||
|
id: None,
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
|
@ -385,6 +385,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
|
||||||
window.dispatch_action(
|
window.dispatch_action(
|
||||||
Box::new(Extensions {
|
Box::new(Extensions {
|
||||||
category_filter: Some(ExtensionCategoryFilter::Themes),
|
category_filter: Some(ExtensionCategoryFilter::Themes),
|
||||||
|
id: None,
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
|
@ -746,6 +746,23 @@ fn handle_open_request(request: OpenRequest, app_state: Arc<AppState>, cx: &mut
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(extension) = request.extension_id {
|
||||||
|
cx.spawn(async move |cx| {
|
||||||
|
let workspace = workspace::get_any_active_workspace(app_state, cx.clone()).await?;
|
||||||
|
workspace.update(cx, |_, window, cx| {
|
||||||
|
window.dispatch_action(
|
||||||
|
Box::new(zed_actions::Extensions {
|
||||||
|
category_filter: None,
|
||||||
|
id: Some(extension),
|
||||||
|
}),
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(connection_options) = request.ssh_connection {
|
if let Some(connection_options) = request.ssh_connection {
|
||||||
cx.spawn(async move |mut cx| {
|
cx.spawn(async move |mut cx| {
|
||||||
let paths: Vec<PathBuf> = request.open_paths.into_iter().map(PathBuf::from).collect();
|
let paths: Vec<PathBuf> = request.open_paths.into_iter().map(PathBuf::from).collect();
|
||||||
|
|
|
@ -37,6 +37,7 @@ pub struct OpenRequest {
|
||||||
pub join_channel: Option<u64>,
|
pub join_channel: Option<u64>,
|
||||||
pub ssh_connection: Option<SshConnectionOptions>,
|
pub ssh_connection: Option<SshConnectionOptions>,
|
||||||
pub dock_menu_action: Option<usize>,
|
pub dock_menu_action: Option<usize>,
|
||||||
|
pub extension_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenRequest {
|
impl OpenRequest {
|
||||||
|
@ -54,6 +55,8 @@ impl OpenRequest {
|
||||||
} else if let Some(file) = url.strip_prefix("zed://ssh") {
|
} else if let Some(file) = url.strip_prefix("zed://ssh") {
|
||||||
let ssh_url = "ssh:/".to_string() + file;
|
let ssh_url = "ssh:/".to_string() + file;
|
||||||
this.parse_ssh_file_path(&ssh_url, cx)?
|
this.parse_ssh_file_path(&ssh_url, cx)?
|
||||||
|
} else if let Some(file) = url.strip_prefix("zed://extension/") {
|
||||||
|
this.extension_id = Some(file.to_string())
|
||||||
} else if url.starts_with("ssh://") {
|
} else if url.starts_with("ssh://") {
|
||||||
this.parse_ssh_file_path(&url, cx)?
|
this.parse_ssh_file_path(&url, cx)?
|
||||||
} else if let Some(request_path) = parse_zed_link(&url, cx) {
|
} else if let Some(request_path) = parse_zed_link(&url, cx) {
|
||||||
|
|
|
@ -76,6 +76,9 @@ pub struct Extensions {
|
||||||
/// Filters the extensions page down to extensions that are in the specified category.
|
/// Filters the extensions page down to extensions that are in the specified category.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub category_filter: Option<ExtensionCategoryFilter>,
|
pub category_filter: Option<ExtensionCategoryFilter>,
|
||||||
|
/// Focuses just the extension with the specified ID.
|
||||||
|
#[serde(default)]
|
||||||
|
pub id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decreases the font size in the editor buffer.
|
/// Decreases the font size in the editor buffer.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue