Allow prompts to have detail, and use for good
Make channel panel errors louder
This commit is contained in:
parent
df420c3767
commit
01424a62ea
17 changed files with 162 additions and 65 deletions
|
@ -2959,6 +2959,7 @@ impl InlineAssistant {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Info,
|
PromptLevel::Info,
|
||||||
prompt_text.as_str(),
|
prompt_text.as_str(),
|
||||||
|
None,
|
||||||
&["Continue", "Cancel"],
|
&["Continue", "Cancel"],
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -130,7 +130,8 @@ pub fn check(_: &Check, cx: &mut WindowContext) {
|
||||||
} else {
|
} else {
|
||||||
drop(cx.prompt(
|
drop(cx.prompt(
|
||||||
gpui::PromptLevel::Info,
|
gpui::PromptLevel::Info,
|
||||||
"Auto-updates disabled for non-bundled app.",
|
"Could not check for updates",
|
||||||
|
Some("Auto-updates disabled for non-bundled app."),
|
||||||
&["Ok"],
|
&["Ok"],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,10 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
|
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
|
||||||
use project::{Fs, Project};
|
use project::{Fs, Project};
|
||||||
use rpc::proto::{self, PeerId};
|
use rpc::{
|
||||||
|
proto::{self, PeerId},
|
||||||
|
ErrorCode, ErrorExt,
|
||||||
|
};
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -35,7 +38,7 @@ use ui::{
|
||||||
use util::{maybe, ResultExt, TryFutureExt};
|
use util::{maybe, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
notifications::{NotifyResultExt, NotifyTaskExt},
|
notifications::{DetachAndPromptErr, NotifyResultExt, NotifyTaskExt},
|
||||||
Workspace,
|
Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -879,7 +882,7 @@ impl CollabPanel {
|
||||||
.update(cx, |workspace, cx| {
|
.update(cx, |workspace, cx| {
|
||||||
let app_state = workspace.app_state().clone();
|
let app_state = workspace.app_state().clone();
|
||||||
workspace::join_remote_project(project_id, host_user_id, app_state, cx)
|
workspace::join_remote_project(project_id, host_user_id, app_state, cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_prompt_err("Failed to join project", cx, |_, _| None);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
}))
|
}))
|
||||||
|
@ -1017,7 +1020,12 @@ impl CollabPanel {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach_and_notify_err(cx)
|
.detach_and_prompt_err("Failed to grant write access", cx, |e, _| {
|
||||||
|
match e.error_code() {
|
||||||
|
ErrorCode::NeedsCla => Some("This user has not yet signed the CLA at https://zed.dev/cla.".into()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else if role == proto::ChannelRole::Member {
|
} else if role == proto::ChannelRole::Member {
|
||||||
|
@ -1038,7 +1046,7 @@ impl CollabPanel {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach_and_notify_err(cx)
|
.detach_and_prompt_err("Failed to revoke write access", cx, |_, _| None)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1258,7 +1266,11 @@ impl CollabPanel {
|
||||||
app_state,
|
app_state,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_prompt_err(
|
||||||
|
"Failed to join project",
|
||||||
|
cx,
|
||||||
|
|_, _| None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ListEntry::ParticipantScreen { peer_id, .. } => {
|
ListEntry::ParticipantScreen { peer_id, .. } => {
|
||||||
|
@ -1432,7 +1444,7 @@ impl CollabPanel {
|
||||||
fn leave_call(cx: &mut WindowContext) {
|
fn leave_call(cx: &mut WindowContext) {
|
||||||
ActiveCall::global(cx)
|
ActiveCall::global(cx)
|
||||||
.update(cx, |call, cx| call.hang_up(cx))
|
.update(cx, |call, cx| call.hang_up(cx))
|
||||||
.detach_and_log_err(cx);
|
.detach_and_prompt_err("Failed to hang up", cx, |_, _| None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
|
fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -1534,11 +1546,11 @@ impl CollabPanel {
|
||||||
cx: &mut ViewContext<CollabPanel>,
|
cx: &mut ViewContext<CollabPanel>,
|
||||||
) {
|
) {
|
||||||
if let Some(clipboard) = self.channel_clipboard.take() {
|
if let Some(clipboard) = self.channel_clipboard.take() {
|
||||||
self.channel_store.update(cx, |channel_store, cx| {
|
self.channel_store
|
||||||
channel_store
|
.update(cx, |channel_store, cx| {
|
||||||
.move_channel(clipboard.channel_id, Some(to_channel_id), cx)
|
channel_store.move_channel(clipboard.channel_id, Some(to_channel_id), cx)
|
||||||
.detach_and_log_err(cx)
|
})
|
||||||
})
|
.detach_and_prompt_err("Failed to move channel", cx, |_, _| None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1610,7 +1622,12 @@ impl CollabPanel {
|
||||||
"Are you sure you want to remove the channel \"{}\"?",
|
"Are you sure you want to remove the channel \"{}\"?",
|
||||||
channel.name
|
channel.name
|
||||||
);
|
);
|
||||||
let answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
|
let answer = cx.prompt(
|
||||||
|
PromptLevel::Warning,
|
||||||
|
&prompt_message,
|
||||||
|
None,
|
||||||
|
&["Remove", "Cancel"],
|
||||||
|
);
|
||||||
cx.spawn(|this, mut cx| async move {
|
cx.spawn(|this, mut cx| async move {
|
||||||
if answer.await? == 0 {
|
if answer.await? == 0 {
|
||||||
channel_store
|
channel_store
|
||||||
|
@ -1631,7 +1648,12 @@ impl CollabPanel {
|
||||||
"Are you sure you want to remove \"{}\" from your contacts?",
|
"Are you sure you want to remove \"{}\" from your contacts?",
|
||||||
github_login
|
github_login
|
||||||
);
|
);
|
||||||
let answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
|
let answer = cx.prompt(
|
||||||
|
PromptLevel::Warning,
|
||||||
|
&prompt_message,
|
||||||
|
None,
|
||||||
|
&["Remove", "Cancel"],
|
||||||
|
);
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
if answer.await? == 0 {
|
if answer.await? == 0 {
|
||||||
user_store
|
user_store
|
||||||
|
@ -1641,7 +1663,7 @@ impl CollabPanel {
|
||||||
}
|
}
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_prompt_err("Failed to remove contact", cx, |_, _| None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn respond_to_contact_request(
|
fn respond_to_contact_request(
|
||||||
|
@ -1654,7 +1676,7 @@ impl CollabPanel {
|
||||||
.update(cx, |store, cx| {
|
.update(cx, |store, cx| {
|
||||||
store.respond_to_contact_request(user_id, accept, cx)
|
store.respond_to_contact_request(user_id, accept, cx)
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_prompt_err("Failed to respond to contact request", cx, |_, _| None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn respond_to_channel_invite(
|
fn respond_to_channel_invite(
|
||||||
|
@ -1675,7 +1697,7 @@ impl CollabPanel {
|
||||||
.update(cx, |call, cx| {
|
.update(cx, |call, cx| {
|
||||||
call.invite(recipient_user_id, Some(self.project.clone()), cx)
|
call.invite(recipient_user_id, Some(self.project.clone()), cx)
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_prompt_err("Call failed", cx, |_, _| None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
|
fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -1691,7 +1713,7 @@ impl CollabPanel {
|
||||||
Some(handle),
|
Some(handle),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.detach_and_log_err(cx)
|
.detach_and_prompt_err("Failed to join channel", cx, |_, _| None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -1704,7 +1726,7 @@ impl CollabPanel {
|
||||||
panel.update(cx, |panel, cx| {
|
panel.update(cx, |panel, cx| {
|
||||||
panel
|
panel
|
||||||
.select_channel(channel_id, None, cx)
|
.select_channel(channel_id, None, cx)
|
||||||
.detach_and_log_err(cx);
|
.detach_and_notify_err(cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1981,7 +2003,7 @@ impl CollabPanel {
|
||||||
.update(cx, |channel_store, cx| {
|
.update(cx, |channel_store, cx| {
|
||||||
channel_store.move_channel(dragged_channel.id, None, cx)
|
channel_store.move_channel(dragged_channel.id, None, cx)
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx)
|
.detach_and_prompt_err("Failed to move channel", cx, |_, _| None)
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2257,7 +2279,7 @@ impl CollabPanel {
|
||||||
.update(cx, |channel_store, cx| {
|
.update(cx, |channel_store, cx| {
|
||||||
channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
|
channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx)
|
.detach_and_prompt_err("Failed to move channel", cx, |_, _| None)
|
||||||
}))
|
}))
|
||||||
.child(
|
.child(
|
||||||
ListItem::new(channel_id as usize)
|
ListItem::new(channel_id as usize)
|
||||||
|
|
|
@ -14,7 +14,7 @@ use rpc::proto::channel_member;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing};
|
use ui::{prelude::*, Avatar, Checkbox, ContextMenu, ListItem, ListItemSpacing};
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
use workspace::{notifications::NotifyTaskExt, ModalView};
|
use workspace::{notifications::DetachAndPromptErr, ModalView};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
channel_modal,
|
channel_modal,
|
||||||
|
@ -498,7 +498,7 @@ impl ChannelModalDelegate {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach_and_notify_err(cx);
|
.detach_and_prompt_err("Failed to update role", cx, |_, _| None);
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,7 +530,7 @@ impl ChannelModalDelegate {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach_and_notify_err(cx);
|
.detach_and_prompt_err("Failed to remove member", cx, |_, _| None);
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -556,7 +556,7 @@ impl ChannelModalDelegate {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.detach_and_notify_err(cx);
|
.detach_and_prompt_err("Failed to invite member", cx, |_, _| None);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_context_menu(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
fn show_context_menu(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
|
|
|
@ -31,7 +31,8 @@ pub fn init(cx: &mut AppContext) {
|
||||||
|
|
||||||
let prompt = cx.prompt(
|
let prompt = cx.prompt(
|
||||||
PromptLevel::Info,
|
PromptLevel::Info,
|
||||||
&format!("Copied into clipboard:\n\n{specs}"),
|
"Copied into clipboard",
|
||||||
|
Some(&specs),
|
||||||
&["OK"],
|
&["OK"],
|
||||||
);
|
);
|
||||||
cx.spawn(|_, _cx| async move {
|
cx.spawn(|_, _cx| async move {
|
||||||
|
|
|
@ -97,7 +97,7 @@ impl ModalView for FeedbackModal {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", &["Yes", "No"]);
|
let answer = cx.prompt(PromptLevel::Info, "Discard feedback?", None, &["Yes", "No"]);
|
||||||
|
|
||||||
cx.spawn(move |this, mut cx| async move {
|
cx.spawn(move |this, mut cx| async move {
|
||||||
if answer.await.ok() == Some(0) {
|
if answer.await.ok() == Some(0) {
|
||||||
|
@ -222,6 +222,7 @@ impl FeedbackModal {
|
||||||
let answer = cx.prompt(
|
let answer = cx.prompt(
|
||||||
PromptLevel::Info,
|
PromptLevel::Info,
|
||||||
"Ready to submit your feedback?",
|
"Ready to submit your feedback?",
|
||||||
|
None,
|
||||||
&["Yes, Submit!", "No"],
|
&["Yes, Submit!", "No"],
|
||||||
);
|
);
|
||||||
let client = cx.global::<Arc<Client>>().clone();
|
let client = cx.global::<Arc<Client>>().clone();
|
||||||
|
@ -255,6 +256,7 @@ impl FeedbackModal {
|
||||||
let prompt = cx.prompt(
|
let prompt = cx.prompt(
|
||||||
PromptLevel::Critical,
|
PromptLevel::Critical,
|
||||||
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
FEEDBACK_SUBMISSION_ERROR_TEXT,
|
||||||
|
None,
|
||||||
&["OK"],
|
&["OK"],
|
||||||
);
|
);
|
||||||
cx.spawn(|_, _cx| async move {
|
cx.spawn(|_, _cx| async move {
|
||||||
|
|
|
@ -150,7 +150,13 @@ pub(crate) trait PlatformWindow {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
|
fn set_input_handler(&mut self, input_handler: PlatformInputHandler);
|
||||||
fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
|
fn take_input_handler(&mut self) -> Option<PlatformInputHandler>;
|
||||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
fn prompt(
|
||||||
|
&self,
|
||||||
|
level: PromptLevel,
|
||||||
|
msg: &str,
|
||||||
|
detail: Option<&str>,
|
||||||
|
answers: &[&str],
|
||||||
|
) -> oneshot::Receiver<usize>;
|
||||||
fn activate(&self);
|
fn activate(&self);
|
||||||
fn set_title(&mut self, title: &str);
|
fn set_title(&mut self, title: &str);
|
||||||
fn set_edited(&mut self, edited: bool);
|
fn set_edited(&mut self, edited: bool);
|
||||||
|
|
|
@ -772,7 +772,13 @@ impl PlatformWindow for MacWindow {
|
||||||
self.0.as_ref().lock().input_handler.take()
|
self.0.as_ref().lock().input_handler.take()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize> {
|
fn prompt(
|
||||||
|
&self,
|
||||||
|
level: PromptLevel,
|
||||||
|
msg: &str,
|
||||||
|
detail: Option<&str>,
|
||||||
|
answers: &[&str],
|
||||||
|
) -> oneshot::Receiver<usize> {
|
||||||
// macOs applies overrides to modal window buttons after they are added.
|
// macOs applies overrides to modal window buttons after they are added.
|
||||||
// Two most important for this logic are:
|
// Two most important for this logic are:
|
||||||
// * Buttons with "Cancel" title will be displayed as the last buttons in the modal
|
// * Buttons with "Cancel" title will be displayed as the last buttons in the modal
|
||||||
|
@ -808,6 +814,9 @@ impl PlatformWindow for MacWindow {
|
||||||
};
|
};
|
||||||
let _: () = msg_send![alert, setAlertStyle: alert_style];
|
let _: () = msg_send![alert, setAlertStyle: alert_style];
|
||||||
let _: () = msg_send![alert, setMessageText: ns_string(msg)];
|
let _: () = msg_send![alert, setMessageText: ns_string(msg)];
|
||||||
|
if let Some(detail) = detail {
|
||||||
|
let _: () = msg_send![alert, setInformativeText: ns_string(detail)];
|
||||||
|
}
|
||||||
|
|
||||||
for (ix, answer) in answers
|
for (ix, answer) in answers
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -185,6 +185,7 @@ impl PlatformWindow for TestWindow {
|
||||||
&self,
|
&self,
|
||||||
_level: crate::PromptLevel,
|
_level: crate::PromptLevel,
|
||||||
_msg: &str,
|
_msg: &str,
|
||||||
|
_detail: Option<&str>,
|
||||||
_answers: &[&str],
|
_answers: &[&str],
|
||||||
) -> futures::channel::oneshot::Receiver<usize> {
|
) -> futures::channel::oneshot::Receiver<usize> {
|
||||||
self.0
|
self.0
|
||||||
|
|
|
@ -1478,9 +1478,12 @@ impl<'a> WindowContext<'a> {
|
||||||
&self,
|
&self,
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
message: &str,
|
message: &str,
|
||||||
|
detail: Option<&str>,
|
||||||
answers: &[&str],
|
answers: &[&str],
|
||||||
) -> oneshot::Receiver<usize> {
|
) -> oneshot::Receiver<usize> {
|
||||||
self.window.platform_window.prompt(level, message, answers)
|
self.window
|
||||||
|
.platform_window
|
||||||
|
.prompt(level, message, detail, answers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all available actions for the focused element.
|
/// Returns all available actions for the focused element.
|
||||||
|
|
|
@ -778,6 +778,7 @@ impl ProjectPanel {
|
||||||
let answer = cx.prompt(
|
let answer = cx.prompt(
|
||||||
PromptLevel::Info,
|
PromptLevel::Info,
|
||||||
&format!("Delete {file_name:?}?"),
|
&format!("Delete {file_name:?}?"),
|
||||||
|
None,
|
||||||
&["Delete", "Cancel"],
|
&["Delete", "Cancel"],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -209,6 +209,7 @@ enum ErrorCode {
|
||||||
UpgradeRequired = 4;
|
UpgradeRequired = 4;
|
||||||
Forbidden = 5;
|
Forbidden = 5;
|
||||||
WrongReleaseChannel = 6;
|
WrongReleaseChannel = 6;
|
||||||
|
NeedsCla = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Test {
|
message Test {
|
||||||
|
|
|
@ -746,6 +746,7 @@ impl ProjectSearchView {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Info,
|
PromptLevel::Info,
|
||||||
prompt_text.as_str(),
|
prompt_text.as_str(),
|
||||||
|
None,
|
||||||
&["Continue", "Cancel"],
|
&["Continue", "Cancel"],
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{Toast, Workspace};
|
use crate::{Toast, Workspace};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter, Render,
|
AnyView, AppContext, AsyncWindowContext, DismissEvent, Entity, EntityId, EventEmitter,
|
||||||
Task, View, ViewContext, VisualContext, WindowContext,
|
PromptLevel, Render, Task, View, ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
use std::{any::TypeId, ops::DerefMut};
|
use std::{any::TypeId, ops::DerefMut};
|
||||||
|
|
||||||
|
@ -299,7 +299,7 @@ pub trait NotifyTaskExt {
|
||||||
|
|
||||||
impl<R, E> NotifyTaskExt for Task<Result<R, E>>
|
impl<R, E> NotifyTaskExt for Task<Result<R, E>>
|
||||||
where
|
where
|
||||||
E: std::fmt::Debug + 'static,
|
E: std::fmt::Debug + Sized + 'static,
|
||||||
R: 'static,
|
R: 'static,
|
||||||
{
|
{
|
||||||
fn detach_and_notify_err(self, cx: &mut WindowContext) {
|
fn detach_and_notify_err(self, cx: &mut WindowContext) {
|
||||||
|
@ -307,3 +307,39 @@ where
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait DetachAndPromptErr {
|
||||||
|
fn detach_and_prompt_err(
|
||||||
|
self,
|
||||||
|
msg: &str,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
f: impl FnOnce(&anyhow::Error, &mut WindowContext) -> Option<String> + 'static,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> DetachAndPromptErr for Task<anyhow::Result<R>>
|
||||||
|
where
|
||||||
|
R: 'static,
|
||||||
|
{
|
||||||
|
fn detach_and_prompt_err(
|
||||||
|
self,
|
||||||
|
msg: &str,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
f: impl FnOnce(&anyhow::Error, &mut WindowContext) -> Option<String> + 'static,
|
||||||
|
) {
|
||||||
|
let msg = msg.to_owned();
|
||||||
|
cx.spawn(|mut cx| async move {
|
||||||
|
if let Err(err) = self.await {
|
||||||
|
log::error!("{err:?}");
|
||||||
|
if let Ok(prompt) = cx.update(|cx| {
|
||||||
|
let detail = f(&err, cx)
|
||||||
|
.unwrap_or_else(|| format!("{err:?}. Please try again.", err = err));
|
||||||
|
cx.prompt(PromptLevel::Critical, &msg, Some(&detail), &["Ok"])
|
||||||
|
}) {
|
||||||
|
prompt.await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -870,7 +870,7 @@ impl Pane {
|
||||||
items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
|
items: &mut dyn Iterator<Item = &Box<dyn ItemHandle>>,
|
||||||
all_dirty_items: usize,
|
all_dirty_items: usize,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> String {
|
) -> (String, String) {
|
||||||
/// Quantity of item paths displayed in prompt prior to cutoff..
|
/// Quantity of item paths displayed in prompt prior to cutoff..
|
||||||
const FILE_NAMES_CUTOFF_POINT: usize = 10;
|
const FILE_NAMES_CUTOFF_POINT: usize = 10;
|
||||||
let mut file_names: Vec<_> = items
|
let mut file_names: Vec<_> = items
|
||||||
|
@ -894,10 +894,12 @@ impl Pane {
|
||||||
file_names.push(format!(".. {} files not shown", not_shown_files).into());
|
file_names.push(format!(".. {} files not shown", not_shown_files).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let file_names = file_names.join("\n");
|
(
|
||||||
format!(
|
format!(
|
||||||
"Do you want to save changes to the following {} files?\n{file_names}",
|
"Do you want to save changes to the following {} files?",
|
||||||
all_dirty_items
|
all_dirty_items
|
||||||
|
),
|
||||||
|
file_names.join("\n"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,11 +931,12 @@ impl Pane {
|
||||||
cx.spawn(|pane, mut cx| async move {
|
cx.spawn(|pane, mut cx| async move {
|
||||||
if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
|
if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
|
||||||
let answer = pane.update(&mut cx, |_, cx| {
|
let answer = pane.update(&mut cx, |_, cx| {
|
||||||
let prompt =
|
let (prompt, detail) =
|
||||||
Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
|
Self::file_names_for_prompt(&mut dirty_items.iter(), dirty_items.len(), cx);
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Warning,
|
PromptLevel::Warning,
|
||||||
&prompt,
|
&prompt,
|
||||||
|
Some(&detail),
|
||||||
&["Save all", "Discard all", "Cancel"],
|
&["Save all", "Discard all", "Cancel"],
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
@ -1131,6 +1134,7 @@ impl Pane {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Warning,
|
PromptLevel::Warning,
|
||||||
CONFLICT_MESSAGE,
|
CONFLICT_MESSAGE,
|
||||||
|
None,
|
||||||
&["Overwrite", "Discard", "Cancel"],
|
&["Overwrite", "Discard", "Cancel"],
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
@ -1154,6 +1158,7 @@ impl Pane {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Warning,
|
PromptLevel::Warning,
|
||||||
&prompt,
|
&prompt,
|
||||||
|
None,
|
||||||
&["Save", "Don't Save", "Cancel"],
|
&["Save", "Don't Save", "Cancel"],
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -30,8 +30,8 @@ use gpui::{
|
||||||
DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle,
|
DragMoveEvent, Element, ElementContext, Entity, EntityId, EventEmitter, FocusHandle,
|
||||||
FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
|
FocusableView, GlobalPixels, InteractiveElement, IntoElement, KeyContext, LayoutId,
|
||||||
ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
|
ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Pixels, Point, PromptLevel,
|
||||||
Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
|
Render, SharedString, Size, Styled, Subscription, Task, View, ViewContext, VisualContext,
|
||||||
WindowBounds, WindowContext, WindowHandle, WindowOptions,
|
WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions,
|
||||||
};
|
};
|
||||||
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
|
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -1159,6 +1159,7 @@ impl Workspace {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Warning,
|
PromptLevel::Warning,
|
||||||
"Do you want to leave the current call?",
|
"Do you want to leave the current call?",
|
||||||
|
None,
|
||||||
&["Close window and hang up", "Cancel"],
|
&["Close window and hang up", "Cancel"],
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
@ -1214,7 +1215,7 @@ impl Workspace {
|
||||||
// Override save mode and display "Save all files" prompt
|
// Override save mode and display "Save all files" prompt
|
||||||
if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
|
if save_intent == SaveIntent::Close && dirty_items.len() > 1 {
|
||||||
let answer = workspace.update(&mut cx, |_, cx| {
|
let answer = workspace.update(&mut cx, |_, cx| {
|
||||||
let prompt = Pane::file_names_for_prompt(
|
let (prompt, detail) = Pane::file_names_for_prompt(
|
||||||
&mut dirty_items.iter().map(|(_, handle)| handle),
|
&mut dirty_items.iter().map(|(_, handle)| handle),
|
||||||
dirty_items.len(),
|
dirty_items.len(),
|
||||||
cx,
|
cx,
|
||||||
|
@ -1222,6 +1223,7 @@ impl Workspace {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Warning,
|
PromptLevel::Warning,
|
||||||
&prompt,
|
&prompt,
|
||||||
|
Some(&detail),
|
||||||
&["Save all", "Discard all", "Cancel"],
|
&["Save all", "Discard all", "Cancel"],
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
@ -3887,13 +3889,16 @@ async fn join_channel_internal(
|
||||||
|
|
||||||
if should_prompt {
|
if should_prompt {
|
||||||
if let Some(workspace) = requesting_window {
|
if let Some(workspace) = requesting_window {
|
||||||
let answer = workspace.update(cx, |_, cx| {
|
let answer = workspace
|
||||||
cx.prompt(
|
.update(cx, |_, cx| {
|
||||||
PromptLevel::Warning,
|
cx.prompt(
|
||||||
"Leaving this call will unshare your current project.\nDo you want to switch channels?",
|
PromptLevel::Warning,
|
||||||
&["Yes, Join Channel", "Cancel"],
|
"Do you want to switch channels?",
|
||||||
)
|
Some("Leaving this call will unshare your current project."),
|
||||||
})?.await;
|
&["Yes, Join Channel", "Cancel"],
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.await;
|
||||||
|
|
||||||
if answer == Ok(1) {
|
if answer == Ok(1) {
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
|
@ -3995,23 +4000,27 @@ pub fn join_channel(
|
||||||
if let Some(active_window) = active_window {
|
if let Some(active_window) = active_window {
|
||||||
active_window
|
active_window
|
||||||
.update(&mut cx, |_, cx| {
|
.update(&mut cx, |_, cx| {
|
||||||
let message:SharedString = match err.error_code() {
|
let detail: SharedString = match err.error_code() {
|
||||||
ErrorCode::SignedOut => {
|
ErrorCode::SignedOut => {
|
||||||
"Failed to join channel\n\nPlease sign in to continue.".into()
|
"Please sign in to continue.".into()
|
||||||
},
|
},
|
||||||
ErrorCode::UpgradeRequired => {
|
ErrorCode::UpgradeRequired => {
|
||||||
"Failed to join channel\n\nPlease update to the latest version of Zed to continue.".into()
|
"Your are running an unsupported version of Zed. Please update to continue.".into()
|
||||||
},
|
},
|
||||||
ErrorCode::NoSuchChannel => {
|
ErrorCode::NoSuchChannel => {
|
||||||
"Failed to find channel\n\nPlease check the link and try again.".into()
|
"No matching channel was found. Please check the link and try again.".into()
|
||||||
},
|
},
|
||||||
ErrorCode::Disconnected => "Failed to join channel\n\nPlease check your internet connection and try again.".into(),
|
ErrorCode::Forbidden => {
|
||||||
ErrorCode::WrongReleaseChannel => format!("Failed to join channel\n\nOther people in the channel are using the {} release of Zed, please switch to that release instead.", err.error_tag("required").unwrap_or("other")).into(),
|
"This channel is private, and you do not have access. Please ask someone to add you and try again.".into()
|
||||||
_ => format!("Failed to join channel\n\n{}\n\nPlease try again.", err).into(),
|
},
|
||||||
|
ErrorCode::Disconnected => "Please check your internet connection and try again.".into(),
|
||||||
|
ErrorCode::WrongReleaseChannel => format!("Others in the channel are using the {} release of Zed. Please switch to join this call.", err.error_tag("required").unwrap_or("other")).into(),
|
||||||
|
_ => format!("{}\n\nPlease try again.", err).into(),
|
||||||
};
|
};
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Critical,
|
PromptLevel::Critical,
|
||||||
&message,
|
"Failed to join channel",
|
||||||
|
Some(&detail),
|
||||||
&["Ok"],
|
&["Ok"],
|
||||||
)
|
)
|
||||||
})?
|
})?
|
||||||
|
@ -4238,6 +4247,7 @@ pub fn restart(_: &Restart, cx: &mut AppContext) {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Info,
|
PromptLevel::Info,
|
||||||
"Are you sure you want to restart?",
|
"Are you sure you want to restart?",
|
||||||
|
None,
|
||||||
&["Restart", "Cancel"],
|
&["Restart", "Cancel"],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -370,16 +370,12 @@ fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewCo
|
||||||
}
|
}
|
||||||
|
|
||||||
fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
|
fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
|
||||||
use std::fmt::Write as _;
|
|
||||||
|
|
||||||
let app_name = cx.global::<ReleaseChannel>().display_name();
|
let app_name = cx.global::<ReleaseChannel>().display_name();
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
let version = env!("CARGO_PKG_VERSION");
|
||||||
let mut message = format!("{app_name} {version}");
|
let message = format!("{app_name} {version}");
|
||||||
if let Some(sha) = cx.try_global::<AppCommitSha>() {
|
let detail = cx.try_global::<AppCommitSha>().map(|sha| sha.0.as_ref());
|
||||||
write!(&mut message, "\n\n{}", sha.0).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let prompt = cx.prompt(PromptLevel::Info, &message, &["OK"]);
|
let prompt = cx.prompt(PromptLevel::Info, &message, detail, &["OK"]);
|
||||||
cx.foreground_executor()
|
cx.foreground_executor()
|
||||||
.spawn(async {
|
.spawn(async {
|
||||||
prompt.await.ok();
|
prompt.await.ok();
|
||||||
|
@ -410,6 +406,7 @@ fn quit(_: &Quit, cx: &mut AppContext) {
|
||||||
cx.prompt(
|
cx.prompt(
|
||||||
PromptLevel::Info,
|
PromptLevel::Info,
|
||||||
"Are you sure you want to quit?",
|
"Are you sure you want to quit?",
|
||||||
|
None,
|
||||||
&["Quit", "Cancel"],
|
&["Quit", "Cancel"],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue