For the extension devs: Adds a `RebuildDevExtension` action that triggers a rebuild of an installed dev extension. - If no dev extensions are installed, shows an error notification. - If exactly one dev extension is installed, rebuilds it immediately. - If multiple dev extensions are installed, opens a fuzzy picker to select which one to rebuild. Makes it easy for devs to trigger a rebuild with a keymap once an action is defined with this PR. Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [ ] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [ ] Tests cover the new/changed behavior - [ ] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - Added `RebuildDevExtension` action to rebuild installed dev extensions from the command palette <img width="675" height="310" alt="Screenshot 2026-04-29 at 1 49 54 AM" src="https://github.com/user-attachments/assets/dbf618f7-f590-40aa-bc5c-580eea9f0001" /> <img width="623" height="329" alt="Screenshot 2026-04-29 at 1 50 16 AM" src="https://github.com/user-attachments/assets/b4bb4131-924f-442b-8351-528a87698399" /> --------- Co-authored-by: MrSubidubi <finn@zed.dev>
205 lines
5.2 KiB
Rust
205 lines
5.2 KiB
Rust
use std::{sync::Arc, time::Duration};
|
|
|
|
use gpui::{Action, SharedString};
|
|
use ui::{IconName, IconPosition};
|
|
use zed_actions::OpenBrowser;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum ErrorSeverity {
|
|
Critical,
|
|
Error,
|
|
Warning,
|
|
}
|
|
|
|
impl ErrorSeverity {
|
|
pub fn auto_dismiss_delay(&self) -> Option<Duration> {
|
|
match self {
|
|
ErrorSeverity::Critical => None,
|
|
ErrorSeverity::Error => Some(Duration::from_secs(30)),
|
|
ErrorSeverity::Warning => Some(Duration::from_secs(20)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The behavior triggered when the user invokes an [`ErrorAction`].
|
|
pub enum ErrorActionHandler {
|
|
/// Run the provided callback when the action is invoked.
|
|
/// The notification is still dismissed afterwards by the button's click handler.
|
|
Action(Box<dyn Action>),
|
|
/// Dismiss the notification without running any extra logic.
|
|
Dismiss,
|
|
}
|
|
|
|
/// An icon to display on an action button, with its position relative to the label.
|
|
///
|
|
/// Bundled together so the position is only carried around when an icon is actually set
|
|
/// — there's no meaningful "position" without an icon to position.
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct ActionIcon {
|
|
pub name: IconName,
|
|
pub position: IconPosition,
|
|
}
|
|
|
|
impl ActionIcon {
|
|
/// Show `name` at the start (left) of the action button label.
|
|
pub fn start(name: IconName) -> Self {
|
|
Self {
|
|
name,
|
|
position: IconPosition::Start,
|
|
}
|
|
}
|
|
|
|
/// Show `name` at the end (right) of the action button label.
|
|
pub fn end(name: IconName) -> Self {
|
|
Self {
|
|
name,
|
|
position: IconPosition::End,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct ErrorAction {
|
|
pub label: SharedString,
|
|
pub icon: Option<ActionIcon>,
|
|
pub tooltip: Option<SharedString>,
|
|
pub handler: ErrorActionHandler,
|
|
}
|
|
|
|
impl ErrorAction {
|
|
pub fn new<A: Action + 'static>(label: impl Into<SharedString>, handler: A) -> Self {
|
|
Self {
|
|
label: label.into(),
|
|
icon: None,
|
|
tooltip: None,
|
|
handler: ErrorActionHandler::Action(Box::new(handler)),
|
|
}
|
|
}
|
|
|
|
/// Creates a dismiss-only action labelled "Dismiss".
|
|
///
|
|
/// Useful as a sensible default for [`WorkspaceError::primary_action`] when the error has no
|
|
/// recovery affordance beyond closing the notification.
|
|
pub fn dismiss() -> Self {
|
|
Self {
|
|
label: "Dismiss".into(),
|
|
icon: None,
|
|
tooltip: None,
|
|
handler: ErrorActionHandler::Dismiss,
|
|
}
|
|
}
|
|
|
|
/// Show `icon` at the start (left) of the action button label.
|
|
pub fn with_icon(mut self, icon: IconName) -> Self {
|
|
self.icon = Some(ActionIcon::start(icon));
|
|
self
|
|
}
|
|
|
|
/// Show `icon` at the end (right) of the action button label.
|
|
///
|
|
/// Useful for actions that navigate the user elsewhere — for example the trailing
|
|
/// `⇗` produced by [`Self::link`].
|
|
pub fn with_end_icon(mut self, icon: IconName) -> Self {
|
|
self.icon = Some(ActionIcon::end(icon));
|
|
self
|
|
}
|
|
|
|
pub fn with_tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
|
|
self.tooltip = Some(tooltip.into());
|
|
self
|
|
}
|
|
|
|
pub fn link(label: impl Into<SharedString>, url: impl Into<Arc<str>>) -> Self {
|
|
Self::new(label, OpenBrowser { url: url.into() }).with_end_icon(IconName::ArrowUpRight)
|
|
}
|
|
}
|
|
|
|
pub trait WorkspaceError {
|
|
fn primary_message(&self) -> SharedString;
|
|
|
|
fn secondary_message(&self) -> Option<SharedString> {
|
|
None
|
|
}
|
|
|
|
/// The primary action shown in the error notification.
|
|
///
|
|
/// If in doubt, use [`ErrorAction::dismiss`].
|
|
fn primary_action(&self) -> ErrorAction;
|
|
|
|
fn secondary_action(&self) -> Option<ErrorAction> {
|
|
None
|
|
}
|
|
|
|
fn severity(&self) -> ErrorSeverity;
|
|
}
|
|
|
|
impl WorkspaceError for &'static str {
|
|
fn primary_message(&self) -> SharedString {
|
|
self.to_string().into()
|
|
}
|
|
|
|
fn primary_action(&self) -> ErrorAction {
|
|
ErrorAction::dismiss()
|
|
}
|
|
|
|
fn severity(&self) -> ErrorSeverity {
|
|
ErrorSeverity::Critical
|
|
}
|
|
}
|
|
|
|
impl WorkspaceError for String {
|
|
fn primary_message(&self) -> SharedString {
|
|
self.clone().into()
|
|
}
|
|
|
|
fn primary_action(&self) -> ErrorAction {
|
|
ErrorAction::dismiss()
|
|
}
|
|
|
|
fn severity(&self) -> ErrorSeverity {
|
|
ErrorSeverity::Critical
|
|
}
|
|
}
|
|
|
|
impl WorkspaceError for anyhow::Error {
|
|
fn primary_message(&self) -> SharedString {
|
|
format!("{self}").into()
|
|
}
|
|
|
|
fn primary_action(&self) -> ErrorAction {
|
|
ErrorAction::dismiss()
|
|
}
|
|
|
|
fn severity(&self) -> ErrorSeverity {
|
|
ErrorSeverity::Critical
|
|
}
|
|
}
|
|
|
|
pub struct PortalError {
|
|
message: String,
|
|
}
|
|
|
|
impl PortalError {
|
|
pub fn new(message: impl Into<String>) -> Self {
|
|
Self {
|
|
message: message.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl WorkspaceError for PortalError {
|
|
fn primary_message(&self) -> SharedString {
|
|
self.message.clone().into()
|
|
}
|
|
|
|
fn severity(&self) -> ErrorSeverity {
|
|
ErrorSeverity::Critical
|
|
}
|
|
|
|
fn primary_action(&self) -> ErrorAction {
|
|
ErrorAction::link(
|
|
"See docs",
|
|
"https://zed.dev/docs/linux#i-cant-open-any-files",
|
|
)
|
|
}
|
|
}
|