Require save confirmation and prevent autosave for deleted files (#20742)
* `has_conflict` will now return true if the file has been deleted on disk. This is for treating multi-buffers as conflicted, and also blocks auto-save. * `has_deleted_file` is added so that the single-file buffer save can specifically mention the delete conflict. This does not yet handle discard (#20745). Closes #9101 Closes #9568 Closes #20462 Release Notes: - Improved handling of externally deleted files: auto-save will be disabled, multibuffers will treat this as a save conflict, and single buffers will ask for restore confirmation. Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
parent
ac5ecf5487
commit
369828f51c
6 changed files with 93 additions and 27 deletions
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue