Display prompt when trying to save a conflicting file

This commit is contained in:
Antonio Scandurra
2021-05-12 11:54:48 +02:00
parent c54a49e5d1
commit 62403343fa
6 changed files with 125 additions and 13 deletions

View File

@@ -2,7 +2,7 @@ use crate::{
elements::ElementBox,
executor,
keymap::{self, Keystroke},
platform::{self, WindowOptions},
platform::{self, PromptLevel, WindowOptions},
presenter::Presenter,
util::{post_inc, timeout},
AssetCache, AssetSource, ClipboardItem, FontCache, PathPromptOptions, TextLayoutCache,
@@ -578,6 +578,31 @@ impl MutableAppContext {
self.platform.set_menus(menus);
}
pub fn prompt<F>(
&self,
window_id: usize,
level: PromptLevel,
msg: &str,
answers: &[&str],
done_fn: F,
) where
F: 'static + FnOnce(usize, &mut MutableAppContext),
{
let app = self.weak_self.as_ref().unwrap().upgrade().unwrap();
let foreground = self.foreground.clone();
let (_, window) = &self.presenters_and_platform_windows[&window_id];
window.prompt(
level,
msg,
answers,
Box::new(move |answer| {
foreground
.spawn(async move { (done_fn)(answer, &mut *app.borrow_mut()) })
.detach();
}),
);
}
pub fn prompt_for_paths<F>(&self, options: PathPromptOptions, done_fn: F)
where
F: 'static + FnOnce(Option<Vec<PathBuf>>, &mut MutableAppContext),
@@ -1766,6 +1791,14 @@ impl<'a, T: View> ViewContext<'a, T> {
&self.app.ctx.background
}
pub fn prompt<F>(&self, level: PromptLevel, msg: &str, answers: &[&str], done_fn: F)
where
F: 'static + FnOnce(usize, &mut MutableAppContext),
{
self.app
.prompt(self.window_id, level, msg, answers, done_fn)
}
pub fn prompt_for_paths<F>(&self, options: PathPromptOptions, done_fn: F)
where
F: 'static + FnOnce(Option<Vec<PathBuf>>, &mut MutableAppContext),

View File

@@ -24,7 +24,7 @@ pub mod color;
pub mod json;
pub mod keymap;
mod platform;
pub use platform::{Event, PathPromptOptions};
pub use platform::{Event, PathPromptOptions, PromptLevel};
pub use presenter::{
AfterLayoutContext, Axis, DebugContext, EventContext, LayoutContext, PaintContext,
SizeConstraint, Vector2FExt,

View File

@@ -5,6 +5,7 @@ use crate::{
platform::{self, Event, WindowContext},
Scene,
};
use block::ConcreteBlock;
use cocoa::{
appkit::{
NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
@@ -26,7 +27,8 @@ use objc::{
use pathfinder_geometry::vector::vec2f;
use smol::Timer;
use std::{
cell::RefCell,
cell::{Cell, RefCell},
convert::TryInto,
ffi::c_void,
mem, ptr,
rc::{Rc, Weak},
@@ -272,6 +274,42 @@ impl platform::Window for Window {
fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
self.0.as_ref().borrow_mut().close_callback = Some(callback);
}
fn prompt(
&self,
level: platform::PromptLevel,
msg: &str,
answers: &[&str],
done_fn: Box<dyn FnOnce(usize)>,
) {
unsafe {
let alert: id = msg_send![class!(NSAlert), alloc];
let alert: id = msg_send![alert, init];
let alert_style = match level {
platform::PromptLevel::Info => 1,
platform::PromptLevel::Warning => 0,
platform::PromptLevel::Critical => 2,
};
let _: () = msg_send![alert, setAlertStyle: alert_style];
let _: () = msg_send![alert, setMessageText: ns_string(msg)];
for (ix, answer) in answers.into_iter().enumerate() {
let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
let _: () = msg_send![button, setTag: ix as NSInteger];
}
let done_fn = Cell::new(Some(done_fn));
let block = ConcreteBlock::new(move |answer: NSInteger| {
if let Some(done_fn) = done_fn.take() {
(done_fn)(answer.try_into().unwrap());
}
});
let block = block.copy();
let _: () = msg_send![
alert,
beginSheetModalForWindow: self.0.borrow().native_window
completionHandler: block
];
}
}
}
impl platform::WindowContext for Window {
@@ -515,3 +553,7 @@ async fn synthetic_drag(
}
}
}
unsafe fn ns_string(string: &str) -> id {
NSString::alloc(nil).init_str(string).autorelease()
}

View File

@@ -71,6 +71,13 @@ pub trait Window: WindowContext {
fn on_event(&mut self, callback: Box<dyn FnMut(Event)>);
fn on_resize(&mut self, callback: Box<dyn FnMut(&mut dyn WindowContext)>);
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
fn prompt(
&self,
level: PromptLevel,
msg: &str,
answers: &[&str],
done_fn: Box<dyn FnOnce(usize)>,
);
}
pub trait WindowContext {
@@ -90,6 +97,12 @@ pub struct PathPromptOptions {
pub multiple: bool,
}
pub enum PromptLevel {
Info,
Warning,
Critical,
}
pub trait FontSystem: Send + Sync {
fn load_family(&self, name: &str) -> anyhow::Result<Vec<FontId>>;
fn select_font(

View File

@@ -163,6 +163,8 @@ impl super::Window for Window {
fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
self.close_handlers.push(callback);
}
fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str], _: Box<dyn FnOnce(usize)>) {}
}
pub(crate) fn platform() -> Platform {

View File

@@ -9,8 +9,8 @@ use crate::{
use futures_core::{future::LocalBoxFuture, Future};
use gpui::{
color::rgbu, elements::*, json::to_string_pretty, keymap::Binding, AnyViewHandle, AppContext,
ClipboardItem, Entity, EntityTask, ModelHandle, MutableAppContext, PathPromptOptions, View,
ViewContext, ViewHandle, WeakModelHandle,
ClipboardItem, Entity, EntityTask, ModelHandle, MutableAppContext, PathPromptOptions,
PromptLevel, View, ViewContext, ViewHandle, WeakModelHandle,
};
use log::error;
pub use pane::*;
@@ -573,15 +573,37 @@ impl Workspace {
}
});
return;
}
} else if item.has_conflict(ctx.as_ref()) {
const CONFLICT_MESSAGE: &'static str = "This file has changed on disk since you started editing it. Do you want to overwrite it?";
let task = item.save(None, ctx.as_mut());
ctx.spawn(task, |_, result, _| {
if let Err(e) = result {
error!("failed to save item: {:?}, ", e);
}
})
.detach()
let handle = ctx.handle();
ctx.prompt(
PromptLevel::Warning,
CONFLICT_MESSAGE,
&["Overwrite", "Cancel"],
move |answer, ctx| {
if answer == 0 {
handle.update(ctx, move |_, ctx| {
let task = item.save(None, ctx.as_mut());
ctx.spawn(task, |_, result, _| {
if let Err(e) = result {
error!("failed to save item: {:?}, ", e);
}
})
.detach();
});
}
},
);
} else {
let task = item.save(None, ctx.as_mut());
ctx.spawn(task, |_, result, _| {
if let Err(e) = result {
error!("failed to save item: {:?}, ", e);
}
})
.detach();
}
}
}