on master help sign in
helix-term/commands: implement buffer-close (bc, bclose) (#1035)
c638b6bCole Helbling
helix-term/src/commands.rs+42−8
@@ -1700,8 +1700,7 @@ mod cmd {
1700 buffers_remaining_impl(cx.editor)?
1700 buffers_remaining_impl(cx.editor)?
1701 }
1701 }
1702
1702
1703− cx.editor
1704− .close(view!(cx.editor).id, /* close_buffer */ false);
1703+ cx.editor.close(view!(cx.editor).id);
1705
1704
1706 Ok(())
1705 Ok(())
1707 }
1706 }
@@ -1711,8 +1710,7 @@ mod cmd {
1711 _args: &[&str],
1710 _args: &[&str],
1712 _event: PromptEvent,
1711 _event: PromptEvent,
1713 ) -> anyhow::Result<()> {
1712 ) -> anyhow::Result<()> {
1714− cx.editor
1715− .close(view!(cx.editor).id, /* close_buffer */ false);
1713+ cx.editor.close(view!(cx.editor).id);
1716
1714
1717 Ok(())
1715 Ok(())
1718 }
1716 }
@@ -1730,6 +1728,28 @@ mod cmd {
1730 Ok(())
1728 Ok(())
1731 }
1729 }
1732
1730
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+
1733 fn write_impl<P: AsRef<Path>>(
1753 fn write_impl<P: AsRef<Path>>(
1734 cx: &mut compositor::Context,
1754 cx: &mut compositor::Context,
1735 path: Option<P>,
1755 path: Option<P>,
@@ -1976,7 +1996,7 @@ mod cmd {
1976 // close all views
1996 // close all views
1977 let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
1997 let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect();
1978 for view_id in views {
1998 for view_id in views {
1979− cx.editor.close(view_id, false);
1999+ cx.editor.close(view_id);
1980 }
2000 }
1981 }
2001 }
1982
2002
@@ -2020,7 +2040,7 @@ mod cmd {
2020 // close all views
2040 // close all views
2021 let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
2041 let views: Vec<_> = editor.tree.views().map(|(view, _)| view.id).collect();
2022 for view_id in views {
2042 for view_id in views {
2023− editor.close(view_id, false);
2043+ editor.close(view_id);
2024 }
2044 }
2025
2045
2026 Ok(())
2046 Ok(())
@@ -2332,6 +2352,20 @@ mod cmd {
2332 fun: open,
2352 fun: open,
2333 completer: Some(completers::filename),
2353 completer: Some(completers::filename),
2334 },
2354 },
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+ },
2335 TypableCommand {
2369 TypableCommand {
2336 name: "write",
2370 name: "write",
2337 aliases: &["w"],
2371 aliases: &["w"],
@@ -4914,7 +4948,7 @@ fn wclose(cx: &mut Context) {
4914 }
4948 }
4915 let view_id = view!(cx.editor).id;
4949 let view_id = view!(cx.editor).id;
4916 // close current split
4950 // close current split
4917− cx.editor.close(view_id, /* close_buffer */ false);
4951+ cx.editor.close(view_id);
4918}
4952}
4919
4953
4920fn wonly(cx: &mut Context) {
4954fn wonly(cx: &mut Context) {
@@ -4926,7 +4960,7 @@ fn wonly(cx: &mut Context) {
4926 .collect::<Vec<_>>();
4960 .collect::<Vec<_>>();
4927 for (view_id, focus) in views {
4961 for (view_id, focus) in views {
4928 if !focus {
4962 if !focus {
4929− cx.editor.close(view_id, /* close_buffer */ false);
4963+ cx.editor.close(view_id);
4930 }
4964 }
4931 }
4965 }
4932}
4966}
helix-view/src/editor.rs+86−27
@@ -22,7 +22,7 @@ use anyhow::Error;
22pub use helix_core::diagnostic::Severity;
22pub use helix_core::diagnostic::Severity;
23pub use helix_core::register::Registers;
23pub use helix_core::register::Registers;
24use helix_core::syntax;
24use helix_core::syntax;
25−use helix_core::Position;
25+use helix_core::{Position, Selection};
26
26
27use serde::Deserialize;
27use serde::Deserialize;
28
28
@@ -235,9 +235,28 @@ impl Editor {
235 }
235 }
236 }
236 }
237
237
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+
238 pub fn switch(&mut self, id: DocumentId, action: Action) {
258 pub fn switch(&mut self, id: DocumentId, action: Action) {
239 use crate::tree::Layout;
259 use crate::tree::Layout;
240− use helix_core::Selection;
241
260
242 if !self.documents.contains_key(&id) {
261 if !self.documents.contains_key(&id) {
243 log::error!("cannot switch to document that does not exist (anymore)");
262 log::error!("cannot switch to document that does not exist (anymore)");
@@ -271,22 +290,9 @@ impl Editor {
271 view.jumps.push(jump);
290 view.jumps.push(jump);
272 view.last_accessed_doc = Some(view.doc);
291 view.last_accessed_doc = Some(view.doc);
273 }
292 }
274− view.doc = id;
275− view.offset = Position::default();
276−
277− let (view, doc) = current!(self);
278
293
279− // initialize selection for view
294+ let view_id = view.id;
280− doc.selections
295+ self.replace_document_in_view(view_id, id);
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);
290
296
291 return;
297 return;
292 }
298 }
@@ -318,11 +324,16 @@ impl Editor {
318 self._refresh();
324 self._refresh();
319 }
325 }
320
326
321− fn new_file_from_document(&mut self, action: Action, mut document: Document) -> DocumentId {
327+ fn new_document(&mut self, mut document: Document) -> DocumentId {
322 let id = DocumentId(self.next_document_id);
328 let id = DocumentId(self.next_document_id);
323 self.next_document_id += 1;
329 self.next_document_id += 1;
324 document.id = id;
330 document.id = id;
325 self.documents.insert(id, document);
331 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);
326 self.switch(id, action);
337 self.switch(id, action);
327 id
338 id
328 }
339 }
@@ -392,7 +403,7 @@ impl Editor {
392 Ok(id)
403 Ok(id)
393 }
404 }
394
405
395− pub fn close(&mut self, id: ViewId, close_buffer: bool) {
406+ pub fn close(&mut self, id: ViewId) {
396 let view = self.tree.get(self.tree.focus);
407 let view = self.tree.get(self.tree.focus);
397 // remove selection
408 // remove selection
398 self.documents
409 self.documents
@@ -401,18 +412,66 @@ impl Editor {
401 .selections
412 .selections
402 .remove(&id);
413 .remove(&id);
403
414
404− if close_buffer {
415+ self.tree.remove(id);
405− // get around borrowck issues
416+ self._refresh();
406− let doc = &self.documents[&view.doc];
417+ }
407
418
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+
408− if let Some(language_server) = doc.language_server() {
434+ if let Some(language_server) = doc.language_server() {
409− tokio::spawn(language_server.text_document_did_close(doc.identifier()));
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+ }
410− }
447+ })
448+ .collect::<Vec<_>>();
449+
450+ for view_id in views_to_close {
451+ self.close(view_id);
452+ }
453+
411− self.documents.remove(&view.doc);
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));
412 }
470 }
413
471
414− self.tree.remove(id);
415 self._refresh();
472 self._refresh();
473+
474+ Ok(())
416 }
475 }
417
476
418 pub fn resize(&mut self, area: Rect) {
477 pub fn resize(&mut self, area: Rect) {
helix-view/src/tree.rs+3
@@ -314,6 +314,9 @@ impl Tree {
314
314
315 pub fn recalculate(&mut self) {
315 pub fn recalculate(&mut self) {
316 if self.is_empty() {
316 if self.is_empty() {
317+ // There are no more views, so the tree should focus itself again.
318+ self.focus = self.root;
319+
317 return;
320 return;
318 }
321 }
319
322
helix-view/src/view.rs+4
@@ -54,6 +54,10 @@ impl JumpList {
54 None
54 None
55 }
55 }
56 }
56 }
57+
58+ pub fn remove(&mut self, doc_id: &DocumentId) {
59+ self.jumps.retain(|(other_id, _)| other_id != doc_id);
60+ }
57}
61}
58
62
59#[derive(Debug)]
63#[derive(Debug)]