Add a dedicated action to open files (#22625)
Closes #22531 Closes #22250 Closes #15679 Release Notes: - Add `workspace::OpenFiles` action to enable opening individual files on Linux and Windows
This commit is contained in:
parent
36301442dd
commit
bbb473b8df
8 changed files with 95 additions and 38 deletions
|
@ -1418,6 +1418,11 @@ impl AppContext {
|
||||||
pub fn get_name(&self) -> &'static str {
|
pub fn get_name(&self) -> &'static str {
|
||||||
self.name.as_ref().unwrap()
|
self.name.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the platform file picker supports selecting a mix of files and directories.
|
||||||
|
pub fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||||
|
self.platform.can_select_mixed_files_and_dirs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context for AppContext {
|
impl Context for AppContext {
|
||||||
|
|
|
@ -175,6 +175,7 @@ pub(crate) trait Platform: 'static {
|
||||||
options: PathPromptOptions,
|
options: PathPromptOptions,
|
||||||
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
|
) -> oneshot::Receiver<Result<Option<Vec<PathBuf>>>>;
|
||||||
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
|
fn prompt_for_new_path(&self, directory: &Path) -> oneshot::Receiver<Result<Option<PathBuf>>>;
|
||||||
|
fn can_select_mixed_files_and_dirs(&self) -> bool;
|
||||||
fn reveal_path(&self, path: &Path);
|
fn reveal_path(&self, path: &Path);
|
||||||
fn open_with_system(&self, path: &Path);
|
fn open_with_system(&self, path: &Path);
|
||||||
|
|
||||||
|
|
|
@ -372,6 +372,11 @@ impl<P: LinuxClient + 'static> Platform for P {
|
||||||
done_rx
|
done_rx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||||
|
// org.freedesktop.portal.FileChooser only supports "pick files" and "pick directories".
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn reveal_path(&self, path: &Path) {
|
fn reveal_path(&self, path: &Path) {
|
||||||
self.reveal_path(path.to_owned());
|
self.reveal_path(path.to_owned());
|
||||||
}
|
}
|
||||||
|
|
|
@ -759,6 +759,10 @@ impl Platform for MacPlatform {
|
||||||
done_rx
|
done_rx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn reveal_path(&self, path: &Path) {
|
fn reveal_path(&self, path: &Path) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let path = path.to_path_buf();
|
let path = path.to_path_buf();
|
||||||
|
|
|
@ -299,6 +299,10 @@ impl Platform for TestPlatform {
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn reveal_path(&self, _path: &std::path::Path) {
|
fn reveal_path(&self, _path: &std::path::Path) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
|
@ -407,6 +407,11 @@ impl Platform for WindowsPlatform {
|
||||||
rx
|
rx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn can_select_mixed_files_and_dirs(&self) -> bool {
|
||||||
|
// The FOS_PICKFOLDERS flag toggles between "only files" and "only folders".
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fn reveal_path(&self, path: &Path) {
|
fn reveal_path(&self, path: &Path) {
|
||||||
let Ok(file_full_path) = path.canonicalize() else {
|
let Ok(file_full_path) = path.canonicalize() else {
|
||||||
log::error!("unable to parse file path");
|
log::error!("unable to parse file path");
|
||||||
|
|
|
@ -449,6 +449,10 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn log_err<E: std::fmt::Debug>(error: &E) {
|
||||||
|
log_error_with_caller(*Location::caller(), error, log::Level::Warn);
|
||||||
|
}
|
||||||
|
|
||||||
pub trait TryFutureExt {
|
pub trait TryFutureExt {
|
||||||
fn log_err(self) -> LogErrorFuture<Self>
|
fn log_err(self) -> LogErrorFuture<Self>
|
||||||
where
|
where
|
||||||
|
|
|
@ -34,10 +34,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, AnyView, AnyWeakView, AppContext, AsyncAppContext,
|
transparent_black, Action, AnyView, AnyWeakView, AppContext, AsyncAppContext,
|
||||||
AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId,
|
AsyncWindowContext, Bounds, CursorStyle, Decorations, DragMoveEvent, Entity as _, EntityId,
|
||||||
EventEmitter, Flatten, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke,
|
EventEmitter, FocusHandle, FocusableView, Global, Hsla, KeyContext, Keystroke, ManagedView,
|
||||||
ManagedView, Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render,
|
Model, ModelContext, MouseButton, PathPromptOptions, Point, PromptLevel, Render, ResizeEdge,
|
||||||
ResizeEdge, Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds,
|
Size, Stateful, Subscription, Task, Tiling, View, WeakView, WindowBounds, WindowHandle,
|
||||||
WindowHandle, WindowId, WindowOptions,
|
WindowId, WindowOptions,
|
||||||
};
|
};
|
||||||
pub use item::{
|
pub use item::{
|
||||||
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, PreviewTabsSettings,
|
||||||
|
@ -145,6 +145,7 @@ actions!(
|
||||||
NewTerminal,
|
NewTerminal,
|
||||||
NewWindow,
|
NewWindow,
|
||||||
Open,
|
Open,
|
||||||
|
OpenFiles,
|
||||||
OpenInTerminal,
|
OpenInTerminal,
|
||||||
ReloadActiveItem,
|
ReloadActiveItem,
|
||||||
SaveAs,
|
SaveAs,
|
||||||
|
@ -332,6 +333,42 @@ pub fn init_settings(cx: &mut AppContext) {
|
||||||
TabBarSettings::register(cx);
|
TabBarSettings::register(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prompt_and_open_paths(
|
||||||
|
app_state: Arc<AppState>,
|
||||||
|
options: PathPromptOptions,
|
||||||
|
cx: &mut AppContext,
|
||||||
|
) {
|
||||||
|
let paths = cx.prompt_for_paths(options);
|
||||||
|
cx.spawn(|cx| async move {
|
||||||
|
match paths.await.anyhow().and_then(|res| res) {
|
||||||
|
Ok(Some(paths)) => {
|
||||||
|
cx.update(|cx| {
|
||||||
|
open_paths(&paths, app_state, OpenOptions::default(), cx).detach_and_log_err(cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(err) => {
|
||||||
|
util::log_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();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
init_settings(cx);
|
init_settings(cx);
|
||||||
notifications::init(cx);
|
notifications::init(cx);
|
||||||
|
@ -343,41 +380,33 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
||||||
cx.on_action({
|
cx.on_action({
|
||||||
let app_state = Arc::downgrade(&app_state);
|
let app_state = Arc::downgrade(&app_state);
|
||||||
move |_: &Open, cx: &mut AppContext| {
|
move |_: &Open, cx: &mut AppContext| {
|
||||||
let paths = cx.prompt_for_paths(PathPromptOptions {
|
|
||||||
files: true,
|
|
||||||
directories: true,
|
|
||||||
multiple: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(app_state) = app_state.upgrade() {
|
if let Some(app_state) = app_state.upgrade() {
|
||||||
cx.spawn(move |cx| async move {
|
prompt_and_open_paths(
|
||||||
match Flatten::flatten(paths.await.map_err(|e| e.into())) {
|
app_state,
|
||||||
Ok(Some(paths)) => {
|
PathPromptOptions {
|
||||||
cx.update(|cx| {
|
files: true,
|
||||||
open_paths(&paths, app_state, OpenOptions::default(), cx)
|
directories: true,
|
||||||
.detach_and_log_err(cx)
|
multiple: true,
|
||||||
})
|
},
|
||||||
.ok();
|
cx,
|
||||||
}
|
);
|
||||||
Ok(None) => {}
|
}
|
||||||
Err(err) => {
|
}
|
||||||
cx.update(|cx| {
|
});
|
||||||
if let Some(workspace_window) = cx
|
cx.on_action({
|
||||||
.active_window()
|
let app_state = Arc::downgrade(&app_state);
|
||||||
.and_then(|window| window.downcast::<Workspace>())
|
move |_: &OpenFiles, cx: &mut AppContext| {
|
||||||
{
|
let directories = cx.can_select_mixed_files_and_dirs();
|
||||||
workspace_window
|
if let Some(app_state) = app_state.upgrade() {
|
||||||
.update(cx, |workspace, cx| {
|
prompt_and_open_paths(
|
||||||
workspace.show_portal_error(err.to_string(), cx);
|
app_state,
|
||||||
})
|
PathPromptOptions {
|
||||||
.ok();
|
files: true,
|
||||||
}
|
directories,
|
||||||
})
|
multiple: true,
|
||||||
.ok();
|
},
|
||||||
}
|
cx,
|
||||||
};
|
);
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue