Detailed changes
@@ -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),
@@ -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,
@@ -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()
+}
@@ -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(
@@ -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 {
@@ -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();
+ }
}
}