Change focus to modified docs on quit (#3872)

* Change focus to modified docs on quit

When quitting with modified documents, automatically switch focus to
one of them.

* Update helix-term/src/commands/typed.rs

Co-authored-by: Poliorcetics <poliorcetics@users.noreply.github.com>

* Make it work with buffer-close-all and the like

* Cleanup

Use Cow instead of String, and rename DoesntExist -> DoesNotExist

Co-authored-by: Poliorcetics <poliorcetics@users.noreply.github.com>
pull/4085/head
A-Walrus 2 years ago committed by GitHub
parent 6764744ce9
commit 1d8bb2249b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,7 +2,7 @@ use std::ops::Deref;
use super::*; use super::*;
use helix_view::editor::{Action, ConfigEvent}; use helix_view::editor::{Action, CloseError, ConfigEvent};
use ui::completers::{self, Completer}; use ui::completers::{self, Completer};
#[derive(Clone)] #[derive(Clone)]
@ -71,8 +71,29 @@ fn buffer_close_by_ids_impl(
doc_ids: &[DocumentId], doc_ids: &[DocumentId],
force: bool, force: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
for &doc_id in doc_ids { let (modified_ids, modified_names): (Vec<_>, Vec<_>) = doc_ids
editor.close_document(doc_id, force)?; .iter()
.filter_map(|&doc_id| {
if let Err(CloseError::BufferModified(name)) = editor.close_document(doc_id, force) {
Some((doc_id, name))
} else {
None
}
})
.unzip();
if let Some(first) = modified_ids.first() {
let current = doc!(editor);
// If the current document is unmodified, and there are modified
// documents, switch focus to the first modified doc.
if !modified_ids.contains(&current.id()) {
editor.switch(*first, Action::Replace);
}
bail!(
"{} unsaved buffer(s) remaining: {:?}",
modified_names.len(),
modified_names
);
} }
Ok(()) Ok(())
@ -513,23 +534,26 @@ fn force_write_quit(
force_quit(cx, &[], event) force_quit(cx, &[], event)
} }
/// Results an error if there are modified buffers remaining and sets editor error, /// Results in an error if there are modified buffers remaining and sets editor
/// otherwise returns `Ok(())` /// error, otherwise returns `Ok(())`. If the current document is unmodified,
/// and there are modified documents, switches focus to one of them.
pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> { pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()> {
let modified: Vec<_> = editor let (modified_ids, modified_names): (Vec<_>, Vec<_>) = editor
.documents() .documents()
.filter(|doc| doc.is_modified()) .filter(|doc| doc.is_modified())
.map(|doc| { .map(|doc| (doc.id(), doc.display_name()))
doc.relative_path() .unzip();
.map(|path| path.to_string_lossy().to_string()) if let Some(first) = modified_ids.first() {
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into()) let current = doc!(editor);
}) // If the current document is unmodified, and there are modified
.collect(); // documents, switch focus to the first modified doc.
if !modified.is_empty() { if !modified_ids.contains(&current.id()) {
editor.switch(*first, Action::Replace);
}
bail!( bail!(
"{} unsaved buffer(s) remaining: {:?}", "{} unsaved buffer(s) remaining: {:?}",
modified.len(), modified_names.len(),
modified modified_names
); );
} }
Ok(()) Ok(())

@ -5,6 +5,7 @@ use helix_core::auto_pairs::AutoPairs;
use helix_core::Range; use helix_core::Range;
use serde::de::{self, Deserialize, Deserializer}; use serde::de::{self, Deserialize, Deserializer};
use serde::Serialize; use serde::Serialize;
use std::borrow::Cow;
use std::cell::Cell; use std::cell::Cell;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
@ -1038,6 +1039,12 @@ impl Document {
.map(helix_core::path::get_relative_path) .map(helix_core::path::get_relative_path)
} }
pub fn display_name(&self) -> Cow<'static, str> {
self.relative_path()
.map(|path| path.to_string_lossy().to_string().into())
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
}
// transact(Fn) ? // transact(Fn) ?
// -- LSP methods // -- LSP methods

@ -1,6 +1,6 @@
use crate::{ use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider}, clipboard::{get_clipboard_provider, ClipboardProvider},
document::{Mode, SCRATCH_BUFFER_NAME}, document::Mode,
graphics::{CursorKind, Rect}, graphics::{CursorKind, Rect},
info::Info, info::Info,
input::KeyEvent, input::KeyEvent,
@ -28,7 +28,7 @@ use tokio::{
time::{sleep, Duration, Instant, Sleep}, time::{sleep, Duration, Instant, Sleep},
}; };
use anyhow::{bail, Error}; use anyhow::Error;
pub use helix_core::diagnostic::Severity; pub use helix_core::diagnostic::Severity;
pub use helix_core::register::Registers; pub use helix_core::register::Registers;
@ -711,6 +711,14 @@ pub enum Action {
VerticalSplit, VerticalSplit,
} }
/// Error thrown on failed document closed
pub enum CloseError {
/// Document doesn't exist
DoesNotExist,
/// Buffer is modified
BufferModified(String),
}
impl Editor { impl Editor {
pub fn new( pub fn new(
mut area: Rect, mut area: Rect,
@ -1070,19 +1078,14 @@ impl Editor {
self._refresh(); self._refresh();
} }
pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> { pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
let doc = match self.documents.get(&doc_id) { let doc = match self.documents.get(&doc_id) {
Some(doc) => doc, Some(doc) => doc,
None => bail!("document does not exist"), None => return Err(CloseError::DoesNotExist),
}; };
if !force && doc.is_modified() { if !force && doc.is_modified() {
bail!( return Err(CloseError::BufferModified(doc.display_name().into_owned()));
"buffer {:?} is modified",
doc.relative_path()
.map(|path| path.to_string_lossy().to_string())
.unwrap_or_else(|| SCRATCH_BUFFER_NAME.into())
);
} }
if let Some(language_server) = doc.language_server() { if let Some(language_server) = doc.language_server() {

Loading…
Cancel
Save