aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCole Helbling2021-11-15 15:30:45 +0000
committerGitHub2021-11-15 15:30:45 +0000
commitc638b6b60e69697b7e7957ed1af1ac071c41974b (patch)
tree1c724c7074e46f19b8595df2362648abecceecae
parentcccc1949ebabcbc1b336f370847626c9d6774fbb (diff)
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
-rw-r--r--helix-term/src/commands.rs50
-rw-r--r--helix-view/src/editor.rs113
-rw-r--r--helix-view/src/tree.rs3
-rw-r--r--helix-view/src/view.rs4
4 files changed, 135 insertions, 35 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 8c0a005c..c7aab726 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1700,8 +1700,7 @@ mod cmd {
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 @@ mod cmd {
_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 @@ mod cmd {
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 @@ mod cmd {
// 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 @@ mod cmd {
// 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(())
@@ -2333,6 +2353,20 @@ mod cmd {
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"],
doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
@@ -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);
}
}
}
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index e4015707..4712c52a 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -22,7 +22,7 @@ use anyhow::Error;
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 @@ impl Editor {
}
}
+ 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 @@ impl Editor {
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 @@ impl Editor {
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 @@ impl Editor {
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,18 +412,66 @@ impl Editor {
.selections
.remove(&id);
- if close_buffer {
- // get around borrowck issues
- let doc = &self.documents[&view.doc];
+ self.tree.remove(id);
+ self._refresh();
+ }
- if let Some(language_server) = doc.language_server() {
- tokio::spawn(language_server.text_document_did_close(doc.identifier()));
- }
- self.documents.remove(&view.doc);
+ 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.tree.remove(id);
self._refresh();
+
+ Ok(())
}
pub fn resize(&mut self, area: Rect) {
diff --git a/helix-view/src/tree.rs b/helix-view/src/tree.rs
index 064334b1..de5046ac 100644
--- a/helix-view/src/tree.rs
+++ b/helix-view/src/tree.rs
@@ -314,6 +314,9 @@ impl Tree {
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;
}
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 6a624ded..a77f1562 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -54,6 +54,10 @@ impl JumpList {
None
}
}
+
+ pub fn remove(&mut self, doc_id: &DocumentId) {
+ self.jumps.retain(|(other_id, _)| other_id != doc_id);
+ }
}
#[derive(Debug)]