@@ -186,6 +186,7 @@ pub struct MultiBufferSnapshot {
non_text_state_update_count: usize,
edit_count: usize,
is_dirty: bool,
+ has_deleted_file: bool,
has_conflict: bool,
show_headers: bool,
}
@@ -494,6 +495,10 @@ impl MultiBuffer {
self.read(cx).is_dirty()
}
+ pub fn has_deleted_file(&self, cx: &AppContext) -> bool {
+ self.read(cx).has_deleted_file()
+ }
+
pub fn has_conflict(&self, cx: &AppContext) -> bool {
self.read(cx).has_conflict()
}
@@ -1419,6 +1424,7 @@ impl MultiBuffer {
snapshot.excerpts = Default::default();
snapshot.trailing_excerpt_update_count += 1;
snapshot.is_dirty = false;
+ snapshot.has_deleted_file = false;
snapshot.has_conflict = false;
self.subscriptions.publish_mut([Edit {
@@ -2003,6 +2009,7 @@ impl MultiBuffer {
let mut excerpts_to_edit = Vec::new();
let mut non_text_state_updated = false;
let mut is_dirty = false;
+ let mut has_deleted_file = false;
let mut has_conflict = false;
let mut edited = false;
let mut buffers = self.buffers.borrow_mut();
@@ -2028,6 +2035,7 @@ impl MultiBuffer {
edited |= buffer_edited;
non_text_state_updated |= buffer_non_text_state_updated;
is_dirty |= buffer.is_dirty();
+ has_deleted_file |= buffer.file().map_or(false, |file| file.is_deleted());
has_conflict |= buffer.has_conflict();
}
if edited {
@@ -2037,6 +2045,7 @@ impl MultiBuffer {
snapshot.non_text_state_update_count += 1;
}
snapshot.is_dirty = is_dirty;
+ snapshot.has_deleted_file = has_deleted_file;
snapshot.has_conflict = has_conflict;
excerpts_to_edit.sort_unstable_by_key(|(locator, _, _)| *locator);
@@ -3691,6 +3700,10 @@ impl MultiBufferSnapshot {
self.is_dirty
}
+ pub fn has_deleted_file(&self) -> bool {
+ self.has_deleted_file
+ }
+
pub fn has_conflict(&self) -> bool {
self.has_conflict
}
@@ -228,6 +228,9 @@ pub trait Item: FocusableView + EventEmitter<Self::Event> {
fn is_dirty(&self, _: &AppContext) -> bool {
false
}
+ fn has_deleted_file(&self, _: &AppContext) -> bool {
+ false
+ }
fn has_conflict(&self, _: &AppContext) -> bool {
false
}
@@ -405,6 +408,7 @@ pub trait ItemHandle: 'static + Send {
fn item_id(&self) -> EntityId;
fn to_any(&self) -> AnyView;
fn is_dirty(&self, cx: &AppContext) -> bool;
+ fn has_deleted_file(&self, cx: &AppContext) -> bool;
fn has_conflict(&self, cx: &AppContext) -> bool;
fn can_save(&self, cx: &AppContext) -> bool;
fn save(
@@ -768,6 +772,10 @@ impl<T: Item> ItemHandle for View<T> {
self.read(cx).is_dirty(cx)
}
+ fn has_deleted_file(&self, cx: &AppContext) -> bool {
+ self.read(cx).has_deleted_file(cx)
+ }
+
fn has_conflict(&self, cx: &AppContext) -> bool {
self.read(cx).has_conflict(cx)
}
@@ -1541,18 +1541,25 @@ impl Pane {
const CONFLICT_MESSAGE: &str =
"This file has changed on disk since you started editing it. Do you want to overwrite it?";
+ const DELETED_MESSAGE: &str =
+ "This file has been deleted on disk since you started editing it. Do you want to recreate it?";
+
if save_intent == SaveIntent::Skip {
return Ok(true);
}
- let (mut has_conflict, mut is_dirty, mut can_save, can_save_as) = cx.update(|cx| {
- (
- item.has_conflict(cx),
- item.is_dirty(cx),
- item.can_save(cx),
- item.is_singleton(cx),
- )
- })?;
+ let (mut has_conflict, mut is_dirty, mut can_save, is_singleton, has_deleted_file) = cx
+ .update(|cx| {
+ (
+ item.has_conflict(cx),
+ item.is_dirty(cx),
+ item.can_save(cx),
+ item.is_singleton(cx),
+ item.has_deleted_file(cx),
+ )
+ })?;
+
+ let can_save_as = is_singleton;
// when saving a single buffer, we ignore whether or not it's dirty.
if save_intent == SaveIntent::Save || save_intent == SaveIntent::SaveWithoutFormat {
@@ -1572,22 +1579,45 @@ impl Pane {
let should_format = save_intent != SaveIntent::SaveWithoutFormat;
if has_conflict && can_save {
- let answer = pane.update(cx, |pane, cx| {
- pane.activate_item(item_ix, true, true, cx);
- cx.prompt(
- PromptLevel::Warning,
- CONFLICT_MESSAGE,
- None,
- &["Overwrite", "Discard", "Cancel"],
- )
- })?;
- match answer.await {
- Ok(0) => {
- pane.update(cx, |_, cx| item.save(should_format, project, cx))?
- .await?
+ if has_deleted_file && is_singleton {
+ let answer = pane.update(cx, |pane, cx| {
+ pane.activate_item(item_ix, true, true, cx);
+ cx.prompt(
+ PromptLevel::Warning,
+ DELETED_MESSAGE,
+ None,
+ &["Overwrite", "Close", "Cancel"],
+ )
+ })?;
+ match answer.await {
+ Ok(0) => {
+ pane.update(cx, |_, cx| item.save(should_format, project, cx))?
+ .await?
+ }
+ Ok(1) => {
+ pane.update(cx, |pane, cx| pane.remove_item(item_ix, false, false, cx))?;
+ }
+ _ => return Ok(false),
+ }
+ return Ok(true);
+ } else {
+ let answer = pane.update(cx, |pane, cx| {
+ pane.activate_item(item_ix, true, true, cx);
+ cx.prompt(
+ PromptLevel::Warning,
+ CONFLICT_MESSAGE,
+ None,
+ &["Overwrite", "Discard", "Cancel"],
+ )
+ })?;
+ match answer.await {
+ Ok(0) => {
+ pane.update(cx, |_, cx| item.save(should_format, project, cx))?
+ .await?
+ }
+ Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
+ _ => return Ok(false),
}
- Ok(1) => pane.update(cx, |_, cx| item.reload(project, cx))?.await?,
- _ => return Ok(false),
}
} else if is_dirty && (can_save || can_save_as) {
if save_intent == SaveIntent::Close {