apricotbucket28 2024-07-15 13:36:39 -03:00 committed by GitHub
parent 5d860e2286
commit f3ddd18201
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 248 additions and 75 deletions

5
Cargo.lock generated
View File

@ -341,8 +341,9 @@ dependencies = [
[[package]] [[package]]
name = "ashpd" name = "ashpd"
version = "0.9.0" version = "0.9.1"
source = "git+https://github.com/bilelmoussaoui/ashpd?rev=29f2e1a#29f2e1a6f4b0911f504658f5f4630c02e01b13f2" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe7e0dd0ac5a401dc116ed9f9119cf9decc625600474cb41f0fc0a0050abc9a"
dependencies = [ dependencies = [
"async-fs 2.1.1", "async-fs 2.1.1",
"async-net 2.0.0", "async-net 2.0.0",

View File

@ -274,7 +274,7 @@ zed_actions = { path = "crates/zed_actions" }
alacritty_terminal = "0.23" alacritty_terminal = "0.23"
any_vec = "0.13" any_vec = "0.13"
anyhow = "1.0.57" anyhow = "1.0.57"
ashpd = { git = "https://github.com/bilelmoussaoui/ashpd", rev = "29f2e1a" } ashpd = "0.9.1"
async-compression = { version = "0.4", features = ["gzip", "futures-io"] } async-compression = { version = "0.4", features = ["gzip", "futures-io"] }
async-dispatcher = { version = "0.1" } async-dispatcher = { version = "0.1" }
async-fs = "1.6" async-fs = "1.6"

View File

@ -12,7 +12,7 @@ use editor::{Editor, EditorElement, EditorStyle};
use extension::{ExtensionManifest, ExtensionOperation, ExtensionStore}; use extension::{ExtensionManifest, ExtensionOperation, ExtensionStore};
use fuzzy::{match_strings, StringMatchCandidate}; use fuzzy::{match_strings, StringMatchCandidate};
use gpui::{ use gpui::{
actions, uniform_list, AnyElement, AppContext, EventEmitter, FocusableView, FontStyle, actions, uniform_list, AnyElement, AppContext, EventEmitter, Flatten, FocusableView, FontStyle,
InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle, InteractiveElement, KeyContext, ParentElement, Render, Styled, Task, TextStyle,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext, UniformListScrollHandle, View, ViewContext, VisualContext, WeakView, WhiteSpace, WindowContext,
}; };
@ -24,7 +24,6 @@ use std::time::Duration;
use std::{ops::Range, sync::Arc}; use std::{ops::Range, sync::Arc};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, ContextMenu, PopoverMenu, ToggleButton, Tooltip}; use ui::{prelude::*, ContextMenu, PopoverMenu, ToggleButton, Tooltip};
use util::ResultExt as _;
use workspace::item::TabContentParams; use workspace::item::TabContentParams;
use workspace::{ use workspace::{
item::{Item, ItemEvent}, item::{Item, ItemEvent},
@ -58,9 +57,23 @@ pub fn init(cx: &mut AppContext) {
multiple: false, multiple: false,
}); });
let workspace_handle = cx.view().downgrade();
cx.deref_mut() cx.deref_mut()
.spawn(|mut cx| async move { .spawn(|mut cx| async move {
let extension_path = prompt.await.log_err()??.pop()?; let extension_path =
match Flatten::flatten(prompt.await.map_err(|e| e.into())) {
Ok(Some(mut paths)) => paths.pop()?,
Ok(None) => return None,
Err(err) => {
workspace_handle
.update(&mut cx, |workspace, cx| {
workspace.show_portal_error(err.to_string(), cx);
})
.ok();
return None;
}
};
store store
.update(&mut cx, |store, cx| { .update(&mut cx, |store, cx| {
store store

View File

@ -612,10 +612,11 @@ impl AppContext {
/// Displays a platform modal for selecting paths. /// Displays a platform modal for selecting paths.
/// When one or more paths are selected, they'll be relayed asynchronously via the returned oneshot channel. /// When one or more paths are selected, they'll be relayed asynchronously via the returned oneshot channel.
/// If cancelled, a `None` will be relayed instead. /// If cancelled, a `None` will be relayed instead.
/// May return an error on Linux if the file picker couldn't be opened.
pub fn prompt_for_paths( pub fn prompt_for_paths(
&self, &self,
options: PathPromptOptions, options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> { ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
self.platform.prompt_for_paths(options) self.platform.prompt_for_paths(options)
} }
@ -623,7 +624,11 @@ impl AppContext {
/// The provided directory will be used to set the initial location. /// The provided directory will be used to set the initial location.
/// When a path is selected, it is relayed asynchronously via the returned oneshot channel. /// When a path is selected, it is relayed asynchronously via the returned oneshot channel.
/// If cancelled, a `None` will be relayed instead. /// If cancelled, a `None` will be relayed instead.
pub fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> { /// May return an error on Linux if the file picker couldn't be opened.
pub fn prompt_for_new_path(
&self,
directory: &Path,
) -> oneshot::Receiver<Result<Option<PathBuf>>> {
self.platform.prompt_for_new_path(directory) self.platform.prompt_for_new_path(directory)
} }

View File

@ -137,8 +137,8 @@ pub(crate) trait Platform: 'static {
fn prompt_for_paths( fn prompt_for_paths(
&self, &self,
options: PathPromptOptions, options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>>; ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>>; fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
fn reveal_path(&self, path: &Path); fn reveal_path(&self, path: &Path);
fn on_quit(&self, callback: Box<dyn FnMut()>); fn on_quit(&self, callback: Box<dyn FnMut()>);

View File

@ -21,6 +21,7 @@ use std::{
use anyhow::anyhow; use anyhow::anyhow;
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest}; use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest as OpenUriRequest}; use ashpd::desktop::open_uri::{OpenDirectoryRequest, OpenFileRequest as OpenUriRequest};
use ashpd::desktop::ResponseError;
use ashpd::{url, ActivationToken}; use ashpd::{url, ActivationToken};
use async_task::Runnable; use async_task::Runnable;
use calloop::channel::Channel; use calloop::channel::Channel;
@ -54,6 +55,9 @@ pub(crate) const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(400);
pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0); pub(crate) const DOUBLE_CLICK_DISTANCE: Pixels = px(5.0);
pub(crate) const KEYRING_LABEL: &str = "zed-github-account"; pub(crate) const KEYRING_LABEL: &str = "zed-github-account";
const FILE_PICKER_PORTAL_MISSING: &str =
"Couldn't open file picker due to missing xdg-desktop-portal implementation.";
pub trait LinuxClient { pub trait LinuxClient {
fn compositor_name(&self) -> &'static str; fn compositor_name(&self) -> &'static str;
fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R; fn with_common<R>(&self, f: impl FnOnce(&mut LinuxCommon) -> R) -> R;
@ -256,7 +260,7 @@ impl<P: LinuxClient + 'static> Platform for P {
fn prompt_for_paths( fn prompt_for_paths(
&self, &self,
options: PathPromptOptions, options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> { ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
let (done_tx, done_rx) = oneshot::channel(); let (done_tx, done_rx) = oneshot::channel();
self.foreground_executor() self.foreground_executor()
.spawn(async move { .spawn(async move {
@ -274,7 +278,7 @@ impl<P: LinuxClient + 'static> Platform for P {
} }
}; };
let result = OpenFileRequest::default() let request = match OpenFileRequest::default()
.modal(true) .modal(true)
.title(title) .title(title)
.accept_label("Select") .accept_label("Select")
@ -282,49 +286,68 @@ impl<P: LinuxClient + 'static> Platform for P {
.directory(options.directories) .directory(options.directories)
.send() .send()
.await .await
.ok() {
.and_then(|request| request.response().ok()) Ok(request) => request,
.and_then(|response| { Err(err) => {
let result = match err {
ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
err => err.into(),
};
done_tx.send(Err(result));
return;
}
};
let result = match request.response() {
Ok(response) => Ok(Some(
response response
.uris() .uris()
.iter() .iter()
.map(|uri| uri.to_file_path().ok()) .filter_map(|uri| uri.to_file_path().ok())
.collect() .collect::<Vec<_>>(),
}); )),
Err(ashpd::Error::Response(ResponseError::Cancelled)) => Ok(None),
Err(e) => Err(e.into()),
};
done_tx.send(result); done_tx.send(result);
}) })
.detach(); .detach();
done_rx done_rx
} }
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> { fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>> {
let (done_tx, done_rx) = oneshot::channel(); let (done_tx, done_rx) = oneshot::channel();
let directory = directory.to_owned(); let directory = directory.to_owned();
self.foreground_executor() self.foreground_executor()
.spawn(async move { .spawn(async move {
let request = SaveFileRequest::default() let request = match SaveFileRequest::default()
.modal(true) .modal(true)
.title("Select new path") .title("Select new path")
.accept_label("Accept") .accept_label("Accept")
.current_folder(directory); .current_folder(directory)
.expect("pathbuf should not be nul terminated")
let result = if let Ok(request) = request { .send()
request .await
.send() {
.await Ok(request) => request,
.ok() Err(err) => {
.and_then(|request| request.response().ok()) let result = match err {
.and_then(|response| { ashpd::Error::PortalNotFound(_) => anyhow!(FILE_PICKER_PORTAL_MISSING),
response err => err.into(),
.uris() };
.first() done_tx.send(Err(result));
.and_then(|uri| uri.to_file_path().ok()) return;
}) }
} else {
None
}; };
let result = match request.response() {
Ok(response) => Ok(response
.uris()
.first()
.and_then(|uri| uri.to_file_path().ok())),
Err(ashpd::Error::Response(ResponseError::Cancelled)) => Ok(None),
Err(e) => Err(e.into()),
};
done_tx.send(result); done_tx.send(result);
}) })
.detach(); .detach();

View File

@ -602,7 +602,7 @@ impl Platform for MacPlatform {
fn prompt_for_paths( fn prompt_for_paths(
&self, &self,
options: PathPromptOptions, options: PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<PathBuf>>> { ) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>> {
let (done_tx, done_rx) = oneshot::channel(); let (done_tx, done_rx) = oneshot::channel();
self.foreground_executor() self.foreground_executor()
.spawn(async move { .spawn(async move {
@ -632,7 +632,7 @@ impl Platform for MacPlatform {
}; };
if let Some(done_tx) = done_tx.take() { if let Some(done_tx) = done_tx.take() {
let _ = done_tx.send(result); let _ = done_tx.send(Ok(result));
} }
}); });
let block = block.copy(); let block = block.copy();
@ -643,7 +643,7 @@ impl Platform for MacPlatform {
done_rx done_rx
} }
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Option<PathBuf>> { fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>> {
let directory = directory.to_owned(); let directory = directory.to_owned();
let (done_tx, done_rx) = oneshot::channel(); let (done_tx, done_rx) = oneshot::channel();
self.foreground_executor() self.foreground_executor()
@ -665,7 +665,7 @@ impl Platform for MacPlatform {
} }
if let Some(done_tx) = done_tx.take() { if let Some(done_tx) = done_tx.take() {
let _ = done_tx.send(result); let _ = done_tx.send(Ok(result));
} }
}); });
let block = block.copy(); let block = block.copy();

View File

@ -34,7 +34,7 @@ pub(crate) struct TestPlatform {
#[derive(Default)] #[derive(Default)]
pub(crate) struct TestPrompts { pub(crate) struct TestPrompts {
multiple_choice: VecDeque<oneshot::Sender<usize>>, multiple_choice: VecDeque<oneshot::Sender<usize>>,
new_path: VecDeque<(PathBuf, oneshot::Sender<Option<PathBuf>>)>, new_path: VecDeque<(PathBuf, oneshot::Sender<Result<Option<PathBuf>>>)>,
} }
impl TestPlatform { impl TestPlatform {
@ -80,7 +80,7 @@ impl TestPlatform {
.new_path .new_path
.pop_front() .pop_front()
.expect("no pending new path prompt"); .expect("no pending new path prompt");
tx.send(select_path(&path)).ok(); tx.send(Ok(select_path(&path))).ok();
} }
pub(crate) fn simulate_prompt_answer(&self, response_ix: usize) { pub(crate) fn simulate_prompt_answer(&self, response_ix: usize) {
@ -216,14 +216,14 @@ impl Platform for TestPlatform {
fn prompt_for_paths( fn prompt_for_paths(
&self, &self,
_options: crate::PathPromptOptions, _options: crate::PathPromptOptions,
) -> oneshot::Receiver<Option<Vec<std::path::PathBuf>>> { ) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
unimplemented!() unimplemented!()
} }
fn prompt_for_new_path( fn prompt_for_new_path(
&self, &self,
directory: &std::path::Path, directory: &std::path::Path,
) -> oneshot::Receiver<Option<std::path::PathBuf>> { ) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
self.prompts self.prompts
.borrow_mut() .borrow_mut()

View File

@ -335,7 +335,10 @@ impl Platform for WindowsPlatform {
self.state.borrow_mut().callbacks.open_urls = Some(callback); self.state.borrow_mut().callbacks.open_urls = Some(callback);
} }
fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver<Option<Vec<PathBuf>>> { fn prompt_for_paths(
&self,
options: PathPromptOptions,
) -> Receiver<Result<Option<Vec<PathBuf>>>> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
self.foreground_executor() self.foreground_executor()
@ -374,7 +377,7 @@ impl Platform for WindowsPlatform {
if hr.unwrap_err().code() == HRESULT(0x800704C7u32 as i32) { if hr.unwrap_err().code() == HRESULT(0x800704C7u32 as i32) {
// user canceled error // user canceled error
if let Some(tx) = tx.take() { if let Some(tx) = tx.take() {
tx.send(None).unwrap(); tx.send(Ok(None)).unwrap();
} }
return; return;
} }
@ -393,10 +396,10 @@ impl Platform for WindowsPlatform {
} }
if let Some(tx) = tx.take() { if let Some(tx) = tx.take() {
if paths.len() == 0 { if paths.is_empty() {
tx.send(None).unwrap(); tx.send(Ok(None)).unwrap();
} else { } else {
tx.send(Some(paths)).unwrap(); tx.send(Ok(Some(paths))).unwrap();
} }
} }
}) })
@ -405,27 +408,27 @@ impl Platform for WindowsPlatform {
rx rx
} }
fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Option<PathBuf>> { fn prompt_for_new_path(&self, directory: &Path) -> Receiver<Result<Option<PathBuf>>> {
let directory = directory.to_owned(); let directory = directory.to_owned();
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
self.foreground_executor() self.foreground_executor()
.spawn(async move { .spawn(async move {
unsafe { unsafe {
let Ok(dialog) = show_savefile_dialog(directory) else { let Ok(dialog) = show_savefile_dialog(directory) else {
let _ = tx.send(None); let _ = tx.send(Ok(None));
return; return;
}; };
let Ok(_) = dialog.Show(None) else { let Ok(_) = dialog.Show(None) else {
let _ = tx.send(None); // user cancel let _ = tx.send(Ok(None)); // user cancel
return; return;
}; };
if let Ok(shell_item) = dialog.GetResult() { if let Ok(shell_item) = dialog.GetResult() {
if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) { if let Ok(file) = shell_item.GetDisplayName(SIGDN_FILESYSPATH) {
let _ = tx.send(Some(PathBuf::from(file.to_string().unwrap()))); let _ = tx.send(Ok(Some(PathBuf::from(file.to_string().unwrap()))));
return; return;
} }
} }
let _ = tx.send(None); let _ = tx.send(Ok(None));
} }
}) })
.detach(); .detach();

View File

@ -160,14 +160,23 @@ impl Workspace {
self.show_notification( self.show_notification(
NotificationId::unique::<WorkspaceErrorNotification>(), NotificationId::unique::<WorkspaceErrorNotification>(),
cx, cx,
|cx| { |cx| cx.new_view(|_cx| ErrorMessagePrompt::new(format!("Error: {err:#}"))),
cx.new_view(|_cx| {
simple_message_notification::MessageNotification::new(format!("Error: {err:#}"))
})
},
); );
} }
pub fn show_portal_error(&mut self, err: String, cx: &mut ViewContext<Self>) {
struct PortalError;
self.show_notification(NotificationId::unique::<PortalError>(), cx, |cx| {
cx.new_view(|_cx| {
ErrorMessagePrompt::new(err.to_string()).with_link_button(
"See docs",
"https://zed.dev/docs/linux#i-cant-open-any-files",
)
})
});
}
pub fn dismiss_notification(&mut self, id: &NotificationId, cx: &mut ViewContext<Self>) { pub fn dismiss_notification(&mut self, id: &NotificationId, cx: &mut ViewContext<Self>) {
self.dismiss_notification_internal(id, cx) self.dismiss_notification_internal(id, cx)
} }
@ -362,6 +371,84 @@ impl Render for LanguageServerPrompt {
impl EventEmitter<DismissEvent> for LanguageServerPrompt {} impl EventEmitter<DismissEvent> for LanguageServerPrompt {}
pub struct ErrorMessagePrompt {
message: SharedString,
label_and_url_button: Option<(SharedString, SharedString)>,
}
impl ErrorMessagePrompt {
pub fn new<S>(message: S) -> Self
where
S: Into<SharedString>,
{
Self {
message: message.into(),
label_and_url_button: None,
}
}
pub fn with_link_button<S>(mut self, label: S, url: S) -> Self
where
S: Into<SharedString>,
{
self.label_and_url_button = Some((label.into(), url.into()));
self
}
}
impl Render for ErrorMessagePrompt {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
h_flex()
.id("error_message_prompt_notification")
.occlude()
.elevation_3(cx)
.items_start()
.justify_between()
.p_2()
.gap_2()
.w_full()
.child(
v_flex()
.w_full()
.child(
h_flex()
.w_full()
.justify_between()
.child(
svg()
.size(cx.text_style().font_size)
.flex_none()
.mr_2()
.mt(px(-2.0))
.map(|icon| {
icon.path(IconName::ExclamationTriangle.path())
.text_color(Color::Error.color(cx))
}),
)
.child(
ui::IconButton::new("close", ui::IconName::Close)
.on_click(cx.listener(|_, _, cx| cx.emit(gpui::DismissEvent))),
),
)
.child(
div()
.max_w_80()
.child(Label::new(self.message.clone()).size(LabelSize::Small)),
)
.when_some(self.label_and_url_button.clone(), |elm, (label, url)| {
elm.child(
div().mt_2().child(
ui::Button::new("error_message_prompt_notification_button", label)
.on_click(move |_, cx| cx.open_url(&url)),
),
)
}),
)
}
}
impl EventEmitter<DismissEvent> for ErrorMessagePrompt {}
pub mod simple_message_notification { pub mod simple_message_notification {
use gpui::{ use gpui::{
div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString, div, DismissEvent, EventEmitter, InteractiveElement, ParentElement, Render, SharedString,

View File

@ -30,10 +30,10 @@ use gpui::{
action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size, action_as, actions, canvas, impl_action_as, impl_actions, point, relative, size,
transparent_black, Action, AnyElement, AnyView, AnyWeakView, AppContext, AsyncAppContext, transparent_black, Action, AnyElement, AnyView, AnyWeakView, AppContext, AsyncAppContext,
AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId, AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId,
EventEmitter, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke, ManagedView, EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke,
Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge, ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render,
Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds, WindowHandle, ResizeEdge, Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds,
WindowOptions, WindowHandle, WindowOptions,
}; };
use item::{ use item::{
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings, FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
@ -305,13 +305,31 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
if let Some(app_state) = app_state.upgrade() { if let Some(app_state) = app_state.upgrade() {
cx.spawn(move |cx| async move { cx.spawn(move |cx| async move {
if let Some(paths) = paths.await.log_err().flatten() { match Flatten::flatten(paths.await.map_err(|e| e.into())) {
cx.update(|cx| { Ok(Some(paths)) => {
open_paths(&paths, app_state, OpenOptions::default(), cx) cx.update(|cx| {
.detach_and_log_err(cx) open_paths(&paths, app_state, OpenOptions::default(), cx)
}) .detach_and_log_err(cx)
.ok(); })
} .ok();
}
Ok(None) => {}
Err(err) => {
cx.update(|cx| {
if let Some(workspace_window) = cx
.active_window()
.and_then(|window| window.downcast::<Workspace>())
{
workspace_window
.update(cx, |workspace, cx| {
workspace.show_portal_error(err.to_string(), cx);
})
.ok();
}
})
.ok();
}
};
}) })
.detach(); .detach();
} }
@ -1321,7 +1339,15 @@ impl Workspace {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let abs_path = cx.prompt_for_new_path(&start_abs_path); let abs_path = cx.prompt_for_new_path(&start_abs_path);
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let abs_path = abs_path.await?; let abs_path: Option<PathBuf> =
Flatten::flatten(abs_path.await.map_err(|e| e.into())).map_err(|err| {
this.update(&mut cx, |this, cx| {
this.show_portal_error(err.to_string(), cx);
})
.ok();
err
})?;
let project_path = abs_path.and_then(|abs_path| { let project_path = abs_path.and_then(|abs_path| {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.project.update(cx, |project, cx| { this.project.update(cx, |project, cx| {
@ -1610,8 +1636,16 @@ impl Workspace {
}); });
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let Some(paths) = paths.await.log_err().flatten() else { let paths = match Flatten::flatten(paths.await.map_err(|e| e.into())) {
return; Ok(Some(paths)) => paths,
Ok(None) => return,
Err(err) => {
this.update(&mut cx, |this, cx| {
this.show_portal_error(err.to_string(), cx);
})
.ok();
return;
}
}; };
if let Some(task) = this if let Some(task) = this
@ -1773,7 +1807,14 @@ impl Workspace {
multiple: true, multiple: true,
}); });
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
if let Some(paths) = paths.await.log_err().flatten() { let paths = Flatten::flatten(paths.await.map_err(|e| e.into())).map_err(|err| {
this.update(&mut cx, |this, cx| {
this.show_portal_error(err.to_string(), cx);
})
.ok();
err
})?;
if let Some(paths) = paths {
let results = this let results = this
.update(&mut cx, |this, cx| { .update(&mut cx, |this, cx| {
this.open_paths(paths, OpenVisible::All, None, cx) this.open_paths(paths, OpenVisible::All, None, cx)