feat: add guarded and forceful editor restart actions
This commit is contained in:
+99
-1
@@ -736,6 +736,15 @@ core_actions![
|
||||
false,
|
||||
true
|
||||
),
|
||||
("core.restart", Action::Restart, &[Mode::Normal], true),
|
||||
(
|
||||
"core.force_restart",
|
||||
Action::ForceRestart,
|
||||
&[Mode::Normal],
|
||||
true,
|
||||
false,
|
||||
true
|
||||
),
|
||||
(
|
||||
"core.save_and_quit",
|
||||
Action::SaveAndQuit,
|
||||
@@ -788,6 +797,7 @@ const COMMAND_ALIASES: &[(&str, Action)] = &[
|
||||
("w!", Action::ForceSave),
|
||||
("q", Action::Quit),
|
||||
("q!", Action::ForceQuit),
|
||||
("restart!", Action::ForceRestart),
|
||||
("wq", Action::SaveAndQuit),
|
||||
("wq!", Action::ForceSaveAndQuit),
|
||||
("bnext", Action::BufferNext),
|
||||
@@ -1609,6 +1619,7 @@ pub struct Editor {
|
||||
lsp_names: Vec<String>,
|
||||
lsp_label: String,
|
||||
quit: bool,
|
||||
restart: bool,
|
||||
capabilities: Vec<String>,
|
||||
features: HashMap<String, Feature>,
|
||||
bindings: HashMap<Mode, HashMap<Input, Binding>>,
|
||||
@@ -1705,6 +1716,7 @@ impl Editor {
|
||||
lsp_names: Vec::new(),
|
||||
lsp_label: String::new(),
|
||||
quit: false,
|
||||
restart: false,
|
||||
capabilities,
|
||||
features: HashMap::new(),
|
||||
bindings: default_bindings(),
|
||||
@@ -3113,6 +3125,8 @@ impl Editor {
|
||||
| Action::WordWrapToggle
|
||||
| Action::Quit
|
||||
| Action::ForceQuit
|
||||
| Action::Restart
|
||||
| Action::ForceRestart
|
||||
| Action::Plugins
|
||||
| Action::Buffers
|
||||
)
|
||||
@@ -4549,7 +4563,12 @@ impl Editor {
|
||||
self.active.dirty()
|
||||
}
|
||||
pub fn should_quit(&self) -> bool {
|
||||
self.quit
|
||||
self.quit || self.restart
|
||||
}
|
||||
|
||||
/// Whether frontend shutdown should relaunch the executable rather than exit.
|
||||
pub fn should_restart(&self) -> bool {
|
||||
self.restart
|
||||
}
|
||||
|
||||
pub fn context(&self) -> Context {
|
||||
@@ -5848,6 +5867,8 @@ impl Editor {
|
||||
| Action::ForceSave
|
||||
| Action::Quit
|
||||
| Action::ForceQuit
|
||||
| Action::Restart
|
||||
| Action::ForceRestart
|
||||
| Action::SaveAndQuit
|
||||
| Action::ForceSaveAndQuit
|
||||
| Action::Plugins
|
||||
@@ -6523,6 +6544,42 @@ impl Editor {
|
||||
self.active.dirty() || self.parked.values().any(Buffer::dirty)
|
||||
}
|
||||
|
||||
fn restart(&mut self, force: bool) {
|
||||
if force || !self.any_dirty() {
|
||||
self.restart = true;
|
||||
return;
|
||||
}
|
||||
let owners: Vec<_> = self
|
||||
.diff_owner
|
||||
.into_iter()
|
||||
.chain(self.tabs.values().filter_map(|tab| tab.diff_owner))
|
||||
.collect();
|
||||
for owner in owners {
|
||||
self.with_pane(owner, |editor| {
|
||||
editor.execute_action(Action::DiffClose.entry(), "");
|
||||
});
|
||||
}
|
||||
if !self.active.dirty() {
|
||||
let dirty = self.buffers().find(|buffer| buffer.dirty).unwrap().id;
|
||||
self.switch_buffer(dirty);
|
||||
}
|
||||
let clean: Vec<_> = self
|
||||
.parked
|
||||
.values()
|
||||
.filter(|buffer| !buffer.dirty())
|
||||
.map(|buffer| buffer.id)
|
||||
.collect();
|
||||
for id in clean {
|
||||
if self.has_buffer(id) {
|
||||
self.delete_buffer(id, false);
|
||||
}
|
||||
}
|
||||
let count = self.buffers().filter(|buffer| buffer.dirty).count();
|
||||
self.set_message(format!(
|
||||
"Restart blocked: {count} buffer(s) have unsaved changes; clean buffers closed. Save them or use :restart!"
|
||||
));
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, command: &str) -> Option<Invocation> {
|
||||
self.pending_lsp_navigation = None;
|
||||
self.finish_diff_prompt();
|
||||
@@ -6748,6 +6805,12 @@ impl Editor {
|
||||
}
|
||||
Action::Quit if self.any_dirty() => self.set_message("Unsaved changes; use :w or :q!"),
|
||||
Action::Quit | Action::ForceQuit => self.quit = true,
|
||||
Action::Restart | Action::ForceRestart if !args.is_empty() => {
|
||||
self.set_message("Restart commands take no arguments");
|
||||
}
|
||||
Action::Restart | Action::ForceRestart => {
|
||||
self.restart(action == Action::ForceRestart);
|
||||
}
|
||||
Action::Plugins if !args.is_empty() => {
|
||||
self.set_message("The plugins command takes no arguments")
|
||||
}
|
||||
@@ -9888,6 +9951,41 @@ mod tests {
|
||||
assert_eq!(fs::read_to_string(path).unwrap(), "new disk");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guarded_restart_closes_historical_views_without_losing_dirty_source_history() {
|
||||
let directory = Directory::new();
|
||||
let path = directory.path("file.txt");
|
||||
fs::write(&path, "disk").unwrap();
|
||||
let mut editor = Editor::open(Some(path.clone()), vec![]).unwrap();
|
||||
editor
|
||||
.apply(vec![Effect::Replace("unsaved".into())])
|
||||
.unwrap();
|
||||
let dirty = editor.buffer_id();
|
||||
historical_diff(
|
||||
&mut editor,
|
||||
&directory.0,
|
||||
vec!["before".into()],
|
||||
vec!["after".into()],
|
||||
);
|
||||
editor.execute("tab.new");
|
||||
let snapshot = editor.buffer_id();
|
||||
assert!(editor.active.diff_snapshot.is_some());
|
||||
editor.execute("restart");
|
||||
assert!(!editor.should_restart());
|
||||
assert!(!editor.has_buffer(snapshot));
|
||||
assert_eq!(editor.buffer_id(), dirty);
|
||||
assert_eq!(editor.lines(), ["unsaved"]);
|
||||
assert!(editor.diff().is_none());
|
||||
assert!(editor.tabs.values().all(|tab| tab.diff().is_none()));
|
||||
editor.handle(Input::Char('u'));
|
||||
assert_eq!(editor.lines(), ["disk"]);
|
||||
assert!(!editor.dirty());
|
||||
assert_eq!(fs::read_to_string(path).unwrap(), "disk");
|
||||
editor.execute("restart");
|
||||
assert!(editor.should_restart());
|
||||
assert!(editor.should_quit());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn notification_escape_preserves_carets_then_ordinary_escape_collapses_them() {
|
||||
let mut editor = editor();
|
||||
|
||||
Reference in New Issue
Block a user