helix-term/commands: implement buffer-close (bc, bclose) (#1035)
* helix-view/view: impl method to remove document from jumps * helix-view/editor: impl close_document * helix-view/editor: remove close_buffer argument from `close` According to archseer, this was never implemented or used properly. Now that we have a proper "buffer close" function, we can get rid of this. * helix-term/commands: implement buffer-close (bc, bclose) This behaves the same as Kakoune's `delete-buffer` / `db` command: * With 3 files opened by the user with `:o ab`, `:o cd`, and `:o ef`: * `buffer-close` once closes `ef` and switches to `cd` * `buffer-close` again closes `cd` and switches to `ab` * `buffer-close` again closes `ab` and switches to a scratch buffer * With 3 files opened from the command line with `hx -- ab cd ef`: * `buffer-close` once closes `ab` and switches to `cd` * `buffer-close` again closes `cd` and switches to `ef` * `buffer-close` again closes `ef` and switches to a scratch buffer * With 1 file opened (`ab`): * `buffer-close` once closes `ab` and switches to a scratch buffer * `buffer-close` again closes the scratch buffer and switches to a new scratch buffer * helix-term/commands: implement buffer-close! (bclose!, bc!) Namely, if you have a document open in multiple splits, all the splits will be closed at the same time, leaving only splits without that document focused (or a scratch buffer if they were all focused on that buffer). * helix-view/tree: reset focus if Tree is empty
This commit is contained in:
parent
cccc1949eb
commit
c638b6b60e
@ -1700,8 +1700,7 @@ fn quit(
|
||||
buffers_remaining_impl(cx.editor)?
|
||||
}
|
||||
|
||||
cx.editor
|
||||
.close(view!(cx.editor).id, /* close_buffer */ false);
|
||||
cx.editor.close(view!(cx.editor).id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1711,8 +1710,7 @@ fn force_quit(
|
||||
_args: &[&str],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
cx.editor
|
||||
.close(view!(cx.editor).id, /* close_buffer */ false);
|
||||
cx.editor.close(view!(cx.editor).id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1730,6 +1728,28 @@ fn open(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn buffer_close(
|
||||
cx: &mut compositor::Context,
|
||||
_args: &[&str],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let view = view!(cx.editor);
|
||||
let doc_id = view.doc;
|
||||
cx.editor.close_document(doc_id, false)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn force_buffer_close(
|
||||
cx: &mut compositor::Context,
|
||||
_args: &[&str],
|
||||
_event: PromptEvent,
|
||||
) -> anyhow::Result<()> {
|
||||
let view = view!(cx.editor);
|
||||
let doc_id = view.doc;
|
||||
cx.editor.close_document(doc_id, true)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_impl<P: AsRef<Path>>(
|
||||
cx: &mut compositor::Context,
|
||||
path: Option<P>,
|
||||
@ -1976,7 +1996,7 @@ fn write_all_impl(
|
||||
// close all views
|
||||
let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
|
||||
for view_id in views {
|
||||
cx.editor.close(view_id, false);
|
||||
cx.editor.close(view_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2020,7 +2040,7 @@ fn quit_all_impl(
|
||||
// close all views
|
||||
let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
|
||||
for view_id in views {
|
||||
editor.close(view_id, false);
|
||||
editor.close(view_id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -2332,6 +2352,20 @@ fn tutor(
|
||||
fun: open,
|
||||
completer: Some(completers::filename),
|
||||
},
|
||||
TypableCommand {
|
||||
name: "buffer-close",
|
||||
aliases: &["bc", "bclose"],
|
||||
doc: "Close the current buffer.",
|
||||
fun: buffer_close,
|
||||
completer: None, // FIXME: buffer completer
|
||||
},
|
||||
TypableCommand {
|
||||
name: "buffer-close!",
|
||||
aliases: &["bc!", "bclose!"],
|
||||
doc: "Close the current buffer forcefully (ignoring unsaved changes).",
|
||||
fun: force_buffer_close,
|
||||
completer: None, // FIXME: buffer completer
|
||||
},
|
||||
TypableCommand {
|
||||
name: "write",
|
||||
aliases: &["w"],
|
||||
@ -4914,7 +4948,7 @@ fn wclose(cx: &mut Context) {
|
||||
}
|
||||
let view_id = view!(cx.editor).id;
|
||||
// close current split
|
||||
cx.editor.close(view_id, /* close_buffer */ false);
|
||||
cx.editor.close(view_id);
|
||||
}
|
||||
|
||||
fn wonly(cx: &mut Context) {
|
||||
@ -4926,7 +4960,7 @@ fn wonly(cx: &mut Context) {
|
||||
.collect::<Vec<_>>();
|
||||
for (view_id, focus) in views {
|
||||
if !focus {
|
||||
cx.editor.close(view_id, /* close_buffer */ false);
|
||||
cx.editor.close(view_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
pub use helix_core::diagnostic::Severity;
|
||||
pub use helix_core::register::Registers;
|
||||
use helix_core::syntax;
|
||||
use helix_core::Position;
|
||||
use helix_core::{Position, Selection};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
@ -235,9 +235,28 @@ fn _refresh(&mut self) {
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) {
|
||||
let view = self.tree.get_mut(current_view);
|
||||
view.doc = doc_id;
|
||||
view.offset = Position::default();
|
||||
|
||||
let doc = self.documents.get_mut(&doc_id).unwrap();
|
||||
|
||||
// initialize selection for view
|
||||
doc.selections
|
||||
.entry(view.id)
|
||||
.or_insert_with(|| Selection::point(0));
|
||||
// TODO: reuse align_view
|
||||
let pos = doc
|
||||
.selection(view.id)
|
||||
.primary()
|
||||
.cursor(doc.text().slice(..));
|
||||
let line = doc.text().char_to_line(pos);
|
||||
view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2);
|
||||
}
|
||||
|
||||
pub fn switch(&mut self, id: DocumentId, action: Action) {
|
||||
use crate::tree::Layout;
|
||||
use helix_core::Selection;
|
||||
|
||||
if !self.documents.contains_key(&id) {
|
||||
log::error!("cannot switch to document that does not exist (anymore)");
|
||||
@ -271,22 +290,9 @@ pub fn switch(&mut self, id: DocumentId, action: Action) {
|
||||
view.jumps.push(jump);
|
||||
view.last_accessed_doc = Some(view.doc);
|
||||
}
|
||||
view.doc = id;
|
||||
view.offset = Position::default();
|
||||
|
||||
let (view, doc) = current!(self);
|
||||
|
||||
// initialize selection for view
|
||||
doc.selections
|
||||
.entry(view.id)
|
||||
.or_insert_with(|| Selection::point(0));
|
||||
// TODO: reuse align_view
|
||||
let pos = doc
|
||||
.selection(view.id)
|
||||
.primary()
|
||||
.cursor(doc.text().slice(..));
|
||||
let line = doc.text().char_to_line(pos);
|
||||
view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2);
|
||||
let view_id = view.id;
|
||||
self.replace_document_in_view(view_id, id);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -318,11 +324,16 @@ pub fn switch(&mut self, id: DocumentId, action: Action) {
|
||||
self._refresh();
|
||||
}
|
||||
|
||||
fn new_file_from_document(&mut self, action: Action, mut document: Document) -> DocumentId {
|
||||
fn new_document(&mut self, mut document: Document) -> DocumentId {
|
||||
let id = DocumentId(self.next_document_id);
|
||||
self.next_document_id += 1;
|
||||
document.id = id;
|
||||
self.documents.insert(id, document);
|
||||
id
|
||||
}
|
||||
|
||||
fn new_file_from_document(&mut self, action: Action, document: Document) -> DocumentId {
|
||||
let id = self.new_document(document);
|
||||
self.switch(id, action);
|
||||
id
|
||||
}
|
||||
@ -392,7 +403,7 @@ pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Erro
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
pub fn close(&mut self, id: ViewId, close_buffer: bool) {
|
||||
pub fn close(&mut self, id: ViewId) {
|
||||
let view = self.tree.get(self.tree.focus);
|
||||
// remove selection
|
||||
self.documents
|
||||
@ -401,20 +412,68 @@ pub fn close(&mut self, id: ViewId, close_buffer: bool) {
|
||||
.selections
|
||||
.remove(&id);
|
||||
|
||||
if close_buffer {
|
||||
// get around borrowck issues
|
||||
let doc = &self.documents[&view.doc];
|
||||
|
||||
if let Some(language_server) = doc.language_server() {
|
||||
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
|
||||
}
|
||||
self.documents.remove(&view.doc);
|
||||
}
|
||||
|
||||
self.tree.remove(id);
|
||||
self._refresh();
|
||||
}
|
||||
|
||||
pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> {
|
||||
let doc = match self.documents.get(&doc_id) {
|
||||
Some(doc) => doc,
|
||||
None => anyhow::bail!("document does not exist"),
|
||||
};
|
||||
|
||||
if !force && doc.is_modified() {
|
||||
anyhow::bail!(
|
||||
"buffer {:?} is modified",
|
||||
doc.relative_path()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|| "[scratch]".into())
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(language_server) = doc.language_server() {
|
||||
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
|
||||
}
|
||||
|
||||
let views_to_close = self
|
||||
.tree
|
||||
.views()
|
||||
.filter_map(|(view, _focus)| {
|
||||
if view.doc == doc_id {
|
||||
Some(view.id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for view_id in views_to_close {
|
||||
self.close(view_id);
|
||||
}
|
||||
|
||||
self.documents.remove(&doc_id);
|
||||
|
||||
// If the document we removed was visible in all views, we will have no more views. We don't
|
||||
// want to close the editor just for a simple buffer close, so we need to create a new view
|
||||
// containing either an existing document, or a brand new document.
|
||||
if self.tree.views().peekable().peek().is_none() {
|
||||
let doc_id = self
|
||||
.documents
|
||||
.iter()
|
||||
.map(|(&doc_id, _)| doc_id)
|
||||
.next()
|
||||
.unwrap_or_else(|| self.new_document(Document::default()));
|
||||
let view = View::new(doc_id);
|
||||
let view_id = self.tree.insert(view);
|
||||
let doc = self.documents.get_mut(&doc_id).unwrap();
|
||||
doc.selections.insert(view_id, Selection::point(0));
|
||||
}
|
||||
|
||||
self._refresh();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, area: Rect) {
|
||||
if self.tree.resize(area) {
|
||||
self._refresh();
|
||||
|
@ -314,6 +314,9 @@ pub fn resize(&mut self, area: Rect) -> bool {
|
||||
|
||||
pub fn recalculate(&mut self) {
|
||||
if self.is_empty() {
|
||||
// There are no more views, so the tree should focus itself again.
|
||||
self.focus = self.root;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,10 @@ pub fn backward(&mut self, view_id: ViewId, doc: &mut Document, count: usize) ->
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, doc_id: &DocumentId) {
|
||||
self.jumps.retain(|(other_id, _)| other_id != doc_id);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
Loading…
Reference in New Issue
Block a user