on master help sign in
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

commitc638b6b60e69697b7e7957ed1af1ac071c41974b authorCole Helbling <cole.e.helbling@outlook.com> dateMon 15 Nov 2021, 07:30 (5 years ago) parentcccc194
4 files changed+135−35
helix-term/src/commands.rs+42−8 helix-view/src/editor.rs+86−27 helix-view/src/tree.rs+3 helix-view/src/view.rs+4
helix-term/src/commands.rs+42−8
@@ -1700,8 +1700,7 @@ mod cmd {
17001700 buffers_remaining_impl(cx.editor)?
17011701 }
17021702
1703− cx.editor
1704− .close(view!(cx.editor).id, /* close_buffer */ false);
1703+ cx.editor.close(view!(cx.editor).id);
17051704
17061705 Ok(())
17071706 }
@@ -1711,8 +1710,7 @@ mod cmd {
17111710 _args: &[&str],
17121711 _event: PromptEvent,
17131712 ) -> anyhow::Result<()> {
1714− cx.editor
1715− .close(view!(cx.editor).id, /* close_buffer */ false);
1713+ cx.editor.close(view!(cx.editor).id);
17161714
17171715 Ok(())
17181716 }
@@ -1730,6 +1728,28 @@ mod cmd {
17301728 Ok(())
17311729 }
17321730
1731+ fn buffer_close(
1732+ cx: &mut compositor::Context,
1733+ _args: &[&str],
1734+ _event: PromptEvent,
1735+ ) -> anyhow::Result<()> {
1736+ let view = view!(cx.editor);
1737+ let doc_id = view.doc;
1738+ cx.editor.close_document(doc_id, false)?;
1739+ Ok(())
1740+ }
1741+
1742+ fn force_buffer_close(
1743+ cx: &mut compositor::Context,
1744+ _args: &[&str],
1745+ _event: PromptEvent,
1746+ ) -> anyhow::Result<()> {
1747+ let view = view!(cx.editor);
1748+ let doc_id = view.doc;
1749+ cx.editor.close_document(doc_id, true)?;
1750+ Ok(())
1751+ }
1752+
17331753 fn write_impl<P: AsRef<Path>>(
17341754 cx: &mut compositor::Context,
17351755 path: Option<P>,
@@ -1976,7 +1996,7 @@ mod cmd {
19761996 // close all views
19771997 let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
19781998 for view_id in views {
1979− cx.editor.close(view_id, false);
1999+ cx.editor.close(view_id);
19802000 }
19812001 }
19822002
@@ -2020,7 +2040,7 @@ mod cmd {
20202040 // close all views
20212041 let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
20222042 for view_id in views {
2023− editor.close(view_id, false);
2043+ editor.close(view_id);
20242044 }
20252045
20262046 Ok(())
@@ -2332,6 +2352,20 @@ mod cmd {
23322352 fun: open,
23332353 completer: Some(completers::filename),
23342354 },
2355+ TypableCommand {
2356+ name: "buffer-close",
2357+ aliases: &["bc", "bclose"],
2358+ doc: "Close the current buffer.",
2359+ fun: buffer_close,
2360+ completer: None, // FIXME: buffer completer
2361+ },
2362+ TypableCommand {
2363+ name: "buffer-close!",
2364+ aliases: &["bc!", "bclose!"],
2365+ doc: "Close the current buffer forcefully (ignoring unsaved changes).",
2366+ fun: force_buffer_close,
2367+ completer: None, // FIXME: buffer completer
2368+ },
23352369 TypableCommand {
23362370 name: "write",
23372371 aliases: &["w"],
@@ -4914,7 +4948,7 @@ fn wclose(cx: &mut Context) {
49144948 }
49154949 let view_id = view!(cx.editor).id;
49164950 // close current split
4917− cx.editor.close(view_id, /* close_buffer */ false);
4951+ cx.editor.close(view_id);
49184952}
49194953
49204954fn wonly(cx: &mut Context) {
@@ -4926,7 +4960,7 @@ fn wonly(cx: &mut Context) {
49264960 .collect::<Vec<_>>();
49274961 for (view_id, focus) in views {
49284962 if !focus {
4929− cx.editor.close(view_id, /* close_buffer */ false);
4963+ cx.editor.close(view_id);
49304964 }
49314965 }
49324966}
helix-view/src/editor.rs+86−27
@@ -22,7 +22,7 @@ use anyhow::Error;
2222pub use helix_core::diagnostic::Severity;
2323pub use helix_core::register::Registers;
2424use helix_core::syntax;
25−use helix_core::Position;
25+use helix_core::{Position, Selection};
2626
2727use serde::Deserialize;
2828
@@ -235,9 +235,28 @@ impl Editor {
235235 }
236236 }
237237
238+ fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) {
239+ let view = self.tree.get_mut(current_view);
240+ view.doc = doc_id;
241+ view.offset = Position::default();
242+
243+ let doc = self.documents.get_mut(&doc_id).unwrap();
244+
245+ // initialize selection for view
246+ doc.selections
247+ .entry(view.id)
248+ .or_insert_with(|| Selection::point(0));
249+ // TODO: reuse align_view
250+ let pos = doc
251+ .selection(view.id)
252+ .primary()
253+ .cursor(doc.text().slice(..));
254+ let line = doc.text().char_to_line(pos);
255+ view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2);
256+ }
257+
238258 pub fn switch(&mut self, id: DocumentId, action: Action) {
239259 use crate::tree::Layout;
240− use helix_core::Selection;
241260
242261 if !self.documents.contains_key(&id) {
243262 log::error!("cannot switch to document that does not exist (anymore)");
@@ -271,22 +290,9 @@ impl Editor {
271290 view.jumps.push(jump);
272291 view.last_accessed_doc = Some(view.doc);
273292 }
274− view.doc = id;
275− view.offset = Position::default();
276−
277− let (view, doc) = current!(self);
278293
279− // initialize selection for view
280− doc.selections
281− .entry(view.id)
282− .or_insert_with(|| Selection::point(0));
283− // TODO: reuse align_view
284− let pos = doc
285− .selection(view.id)
286− .primary()
287− .cursor(doc.text().slice(..));
288− let line = doc.text().char_to_line(pos);
289− view.offset.row = line.saturating_sub(view.inner_area().height as usize / 2);
294+ let view_id = view.id;
295+ self.replace_document_in_view(view_id, id);
290296
291297 return;
292298 }
@@ -318,11 +324,16 @@ impl Editor {
318324 self._refresh();
319325 }
320326
321− fn new_file_from_document(&mut self, action: Action, mut document: Document) -> DocumentId {
327+ fn new_document(&mut self, mut document: Document) -> DocumentId {
322328 let id = DocumentId(self.next_document_id);
323329 self.next_document_id += 1;
324330 document.id = id;
325331 self.documents.insert(id, document);
332+ id
333+ }
334+
335+ fn new_file_from_document(&mut self, action: Action, document: Document) -> DocumentId {
336+ let id = self.new_document(document);
326337 self.switch(id, action);
327338 id
328339 }
@@ -392,7 +403,7 @@ impl Editor {
392403 Ok(id)
393404 }
394405
395− pub fn close(&mut self, id: ViewId, close_buffer: bool) {
406+ pub fn close(&mut self, id: ViewId) {
396407 let view = self.tree.get(self.tree.focus);
397408 // remove selection
398409 self.documents
@@ -401,18 +412,66 @@ impl Editor {
401412 .selections
402413 .remove(&id);
403414
404− if close_buffer {
405− // get around borrowck issues
406− let doc = &self.documents[&view.doc];
415+ self.tree.remove(id);
416+ self._refresh();
417+ }
407418
408− if let Some(language_server) = doc.language_server() {
409− tokio::spawn(language_server.text_document_did_close(doc.identifier()));
410− }
411− self.documents.remove(&view.doc);
419+ pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> {
420+ let doc = match self.documents.get(&doc_id) {
421+ Some(doc) => doc,
422+ None => anyhow::bail!("document does not exist"),
423+ };
424+
425+ if !force && doc.is_modified() {
426+ anyhow::bail!(
427+ "buffer {:?} is modified",
428+ doc.relative_path()
429+ .map(|path| path.to_string_lossy().to_string())
430+ .unwrap_or_else(|| "[scratch]".into())
431+ );
432+ }
433+
434+ if let Some(language_server) = doc.language_server() {
435+ tokio::spawn(language_server.text_document_did_close(doc.identifier()));
436+ }
437+
438+ let views_to_close = self
439+ .tree
440+ .views()
441+ .filter_map(|(view, _focus)| {
442+ if view.doc == doc_id {
443+ Some(view.id)
444+ } else {
445+ None
446+ }
447+ })
448+ .collect::<Vec<_>>();
449+
450+ for view_id in views_to_close {
451+ self.close(view_id);
452+ }
453+
454+ self.documents.remove(&doc_id);
455+
456+ // If the document we removed was visible in all views, we will have no more views. We don't
457+ // want to close the editor just for a simple buffer close, so we need to create a new view
458+ // containing either an existing document, or a brand new document.
459+ if self.tree.views().peekable().peek().is_none() {
460+ let doc_id = self
461+ .documents
462+ .iter()
463+ .map(|(&doc_id, _)| doc_id)
464+ .next()
465+ .unwrap_or_else(|| self.new_document(Document::default()));
466+ let view = View::new(doc_id);
467+ let view_id = self.tree.insert(view);
468+ let doc = self.documents.get_mut(&doc_id).unwrap();
469+ doc.selections.insert(view_id, Selection::point(0));
412470 }
413471
414− self.tree.remove(id);
415472 self._refresh();
473+
474+ Ok(())
416475 }
417476
418477 pub fn resize(&mut self, area: Rect) {
helix-view/src/tree.rs+3
@@ -314,6 +314,9 @@ impl Tree {
314314
315315 pub fn recalculate(&mut self) {
316316 if self.is_empty() {
317+ // There are no more views, so the tree should focus itself again.
318+ self.focus = self.root;
319+
317320 return;
318321 }
319322
helix-view/src/view.rs+4
@@ -54,6 +54,10 @@ impl JumpList {
5454 None
5555 }
5656 }
57+
58+ pub fn remove(&mut self, doc_id: &DocumentId) {
59+ self.jumps.retain(|(other_id, _)| other_id != doc_id);
60+ }
5761}
5862
5963#[derive(Debug)]