on master help sign in
chore(write): serialize write operations within a Document
d706194Skyler Hawthorne
helix-term/src/application.rs+103−32
@@ -6,7 +6,14 @@ use helix_core::{
6 pos_at_coords, syntax, Selection,
6 pos_at_coords, syntax, Selection,
7};
7};
8use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
8use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap};
9−use helix_view::{align_view, editor::ConfigEvent, theme, tree::Layout, Align, Editor};
9+use helix_view::{
10+ align_view,
11+ document::DocumentSaveEventResult,
12+ editor::{ConfigEvent, EditorEvent},
13+ theme,
14+ tree::Layout,
15+ Align, Editor,
16+};
10use serde_json::json;
17use serde_json::json;
11
18
12use crate::{
19use crate::{
@@ -19,7 +26,7 @@ use crate::{
19 ui::{self, overlay::overlayed},
26 ui::{self, overlay::overlayed},
20};
27};
21
28
22−use log::{error, warn};
29+use log::{debug, error, warn};
23use std::{
30use std::{
24 io::{stdin, stdout, Write},
31 io::{stdin, stdout, Write},
25 sync::Arc,
32 sync::Arc,
@@ -294,26 +301,6 @@ impl Application {
294 Some(signal) = self.signals.next() => {
301 Some(signal) = self.signals.next() => {
295 self.handle_signals(signal).await;
302 self.handle_signals(signal).await;
296 }
303 }
297− Some((id, call)) = self.editor.language_servers.incoming.next() => {
298− self.handle_language_server_message(call, id).await;
299− // limit render calls for fast language server messages
300− let last = self.editor.language_servers.incoming.is_empty();
301−
302− if last || self.last_render.elapsed() > LSP_DEADLINE {
303− self.render();
304− self.last_render = Instant::now();
305− }
306− }
307− Some(payload) = self.editor.debugger_events.next() => {
308− let needs_render = self.editor.handle_debugger_message(payload).await;
309− if needs_render {
310− self.render();
311− }
312− }
313− Some(config_event) = self.editor.config_events.1.recv() => {
314− self.handle_config_events(config_event);
315− self.render();
316− }
317 Some(callback) = self.jobs.futures.next() => {
304 Some(callback) = self.jobs.futures.next() => {
318 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
305 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
319 self.render();
306 self.render();
@@ -322,20 +309,47 @@ impl Application {
322 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
309 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
323 self.render();
310 self.render();
324 }
311 }
325− _ = &mut self.editor.idle_timer => {
312+ event = self.editor.wait_event() => {
326− // idle timeout
313+ match event {
314+ EditorEvent::DocumentSave(event) => {
315+ self.handle_document_write(event);
316+ self.render();
317+ }
318+ EditorEvent::ConfigEvent(event) => {
319+ self.handle_config_events(event);
320+ self.render();
321+ }
322+ EditorEvent::LanguageServerMessage((id, call)) => {
323+ self.handle_language_server_message(call, id).await;
324+ // limit render calls for fast language server messages
325+ let last = self.editor.language_servers.incoming.is_empty();
326+
327+ if last || self.last_render.elapsed() > LSP_DEADLINE {
328+ self.render();
329+ self.last_render = Instant::now();
330+ }
331+ }
332+ EditorEvent::DebuggerEvent(payload) => {
333+ let needs_render = self.editor.handle_debugger_message(payload).await;
334+ if needs_render {
335+ self.render();
336+ }
337+ }
338+ EditorEvent::IdleTimer => {
327− self.editor.clear_idle_timer();
339+ self.editor.clear_idle_timer();
328− self.handle_idle_timeout();
340+ self.handle_idle_timeout();
329
341
330− #[cfg(feature = "integration")]
342+ #[cfg(feature = "integration")]
331− {
343+ {
332− idle_handled = true;
344+ idle_handled = true;
345+ }
346+ }
333 }
347 }
334 }
348 }
335 }
349 }
336
350
337 // for integration tests only, reset the idle timer after every
351 // for integration tests only, reset the idle timer after every
338− // event to make a signal when test events are done processing
352+ // event to signal when test events are done processing
339 #[cfg(feature = "integration")]
353 #[cfg(feature = "integration")]
340 {
354 {
341 if idle_handled {
355 if idle_handled {
@@ -446,6 +460,46 @@ impl Application {
446 }
460 }
447 }
461 }
448
462
463+ pub fn handle_document_write(&mut self, doc_save_event: DocumentSaveEventResult) {
464+ if let Err(err) = doc_save_event {
465+ self.editor.set_error(err.to_string());
466+ return;
467+ }
468+
469+ let doc_save_event = doc_save_event.unwrap();
470+ let doc = self.editor.document_mut(doc_save_event.doc_id);
471+
472+ if doc.is_none() {
473+ warn!(
474+ "received document saved event for non-existent doc id: {}",
475+ doc_save_event.doc_id
476+ );
477+
478+ return;
479+ }
480+
481+ let doc = doc.unwrap();
482+
483+ debug!(
484+ "document {:?} saved with revision {}",
485+ doc.path(),
486+ doc_save_event.revision
487+ );
488+
489+ doc.set_last_saved_revision(doc_save_event.revision);
490+ let lines = doc.text().len_lines();
491+ let bytes = doc.text().len_bytes();
492+
493+ let path_str = doc
494+ .path()
495+ .expect("document written without path")
496+ .to_string_lossy()
497+ .into_owned();
498+
499+ self.editor
500+ .set_status(format!("'{}' written, {}L {}B", path_str, lines, bytes));
501+ }
502+
449 pub fn handle_terminal_events(&mut self, event: Result<CrosstermEvent, crossterm::ErrorKind>) {
503 pub fn handle_terminal_events(&mut self, event: Result<CrosstermEvent, crossterm::ErrorKind>) {
450 let mut cx = crate::compositor::Context {
504 let mut cx = crate::compositor::Context {
451 editor: &mut self.editor,
505 editor: &mut self.editor,
@@ -866,11 +920,28 @@ impl Application {
866
920
867 self.event_loop(input_stream).await;
921 self.event_loop(input_stream).await;
868
922
869− let err = self.close().await.err();
923+ let mut save_errs = Vec::new();
924+
925+ for doc in self.editor.documents_mut() {
926+ if let Some(Err(err)) = doc.close().await {
927+ save_errs.push((
928+ doc.path()
929+ .map(|path| path.to_string_lossy().into_owned())
930+ .unwrap_or_else(|| "".into()),
931+ err,
932+ ));
933+ }
934+ }
870
935
936+ let close_err = self.close().await.err();
871 restore_term()?;
937 restore_term()?;
872
938
939+ for (path, err) in save_errs {
940+ self.editor.exit_code = 1;
941+ eprintln!("Error closing '{}': {}", path, err);
942+ }
943+
873− if let Some(err) = err {
944+ if let Some(err) = close_err {
874 self.editor.exit_code = 1;
945 self.editor.exit_code = 1;
875 eprintln!("Error: {}", err);
946 eprintln!("Error: {}", err);
876 }
947 }
helix-term/src/commands.rs+1−1
@@ -51,7 +51,7 @@ use crate::{
51 ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
51 ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
52};
52};
53
53
54−use crate::job::{self, Job, Jobs};
54+use crate::job::{self, Jobs};
55use futures_util::{FutureExt, StreamExt};
55use futures_util::{FutureExt, StreamExt};
56use std::{collections::HashMap, fmt, future::Future};
56use std::{collections::HashMap, fmt, future::Future};
57use std::{collections::HashSet, num::NonZeroUsize};
57use std::{collections::HashSet, num::NonZeroUsize};
helix-term/src/commands/typed.rs+8−5
@@ -77,7 +77,9 @@ fn buffer_close_by_ids_impl(
77 let (modified_ids, modified_names): (Vec<_>, Vec<_>) = doc_ids
77 let (modified_ids, modified_names): (Vec<_>, Vec<_>) = doc_ids
78 .iter()
78 .iter()
79 .filter_map(|&doc_id| {
79 .filter_map(|&doc_id| {
80− if let Err(CloseError::BufferModified(name)) = editor.close_document(doc_id, force) {
80+ if let Err(CloseError::BufferModified(name)) =
81+ helix_lsp::block_on(editor.close_document(doc_id, force))
82+ {
81 Some((doc_id, name))
83 Some((doc_id, name))
82 } else {
84 } else {
83 None
85 None
@@ -269,6 +271,7 @@ fn write_impl(
269 doc.set_path(Some(path.as_ref().as_ref()))
271 doc.set_path(Some(path.as_ref().as_ref()))
270 .context("invalid filepath")?;
272 .context("invalid filepath")?;
271 }
273 }
274+
272 if doc.path().is_none() {
275 if doc.path().is_none() {
273 bail!("cannot write a buffer without a filename");
276 bail!("cannot write a buffer without a filename");
274 }
277 }
@@ -287,8 +290,8 @@ fn write_impl(
287 } else {
290 } else {
288 None
291 None
289 };
292 };
293+
290− let future = doc.format_and_save(fmt, force);
294+ doc.format_and_save(fmt, force)?;
291− cx.jobs.add(Job::new(future).wait_before_exiting());
292
295
293 if path.is_some() {
296 if path.is_some() {
294 let id = doc.id();
297 let id = doc.id();
@@ -602,8 +605,8 @@ fn write_all_impl(
602 } else {
605 } else {
603 None
606 None
604 };
607 };
608+
605− let future = doc.format_and_save(fmt, force);
609+ doc.format_and_save(fmt, force)?;
606− jobs.add(Job::new(future).wait_before_exiting());
607 }
610 }
608
611
609 if quit {
612 if quit {
helix-term/tests/test/write.rs−2
@@ -62,7 +62,6 @@ async fn test_write_quit() -> anyhow::Result<()> {
62}
62}
63
63
64#[tokio::test]
64#[tokio::test]
65−#[ignore]
66async fn test_write_concurrent() -> anyhow::Result<()> {
65async fn test_write_concurrent() -> anyhow::Result<()> {
67 let mut file = tempfile::NamedTempFile::new()?;
66 let mut file = tempfile::NamedTempFile::new()?;
68 let mut command = String::new();
67 let mut command = String::new();
@@ -92,7 +91,6 @@ async fn test_write_concurrent() -> anyhow::Result<()> {
92}
91}
93
92
94#[tokio::test]
93#[tokio::test]
95−#[ignore]
96async fn test_write_fail_mod_flag() -> anyhow::Result<()> {
94async fn test_write_fail_mod_flag() -> anyhow::Result<()> {
97 let file = helpers::new_readonly_tempfile()?;
95 let file = helpers::new_readonly_tempfile()?;
98
96
helix-view/src/document.rs+117−11
@@ -3,6 +3,7 @@ use futures_util::future::BoxFuture;
3use futures_util::FutureExt;
3use futures_util::FutureExt;
4use helix_core::auto_pairs::AutoPairs;
4use helix_core::auto_pairs::AutoPairs;
5use helix_core::Range;
5use helix_core::Range;
6+use log::debug;
6use serde::de::{self, Deserialize, Deserializer};
7use serde::de::{self, Deserialize, Deserializer};
7use serde::Serialize;
8use serde::Serialize;
8use std::borrow::Cow;
9use std::borrow::Cow;
@@ -13,6 +14,8 @@ use std::future::Future;
13use std::path::{Path, PathBuf};
14use std::path::{Path, PathBuf};
14use std::str::FromStr;
15use std::str::FromStr;
15use std::sync::Arc;
16use std::sync::Arc;
17+use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
18+use tokio::sync::Mutex;
16
19
17use helix_core::{
20use helix_core::{
18 encoding,
21 encoding,
@@ -83,6 +86,16 @@ impl Serialize for Mode {
83 }
86 }
84}
87}
85
88
89+/// A snapshot of the text of a document that we want to write out to disk
90+#[derive(Debug, Clone)]
91+pub struct DocumentSaveEvent {
92+ pub revision: usize,
93+ pub doc_id: DocumentId,
94+}
95+
96+pub type DocumentSaveEventResult = Result<DocumentSaveEvent, anyhow::Error>;
97+pub type DocumentSaveEventFuture = BoxFuture<'static, DocumentSaveEventResult>;
98+
86pub struct Document {
99pub struct Document {
87 pub(crate) id: DocumentId,
100 pub(crate) id: DocumentId,
88 text: Rope,
101 text: Rope,
@@ -118,6 +131,9 @@ pub struct Document {
118 last_saved_revision: usize,
131 last_saved_revision: usize,
119 version: i32, // should be usize?
132 version: i32, // should be usize?
120 pub(crate) modified_since_accessed: bool,
133 pub(crate) modified_since_accessed: bool,
134+ save_sender: Option<UnboundedSender<DocumentSaveEventFuture>>,
135+ save_receiver: Option<UnboundedReceiver<DocumentSaveEventFuture>>,
136+ current_save: Arc<Mutex<Option<DocumentSaveEventFuture>>>,
121
137
122 diagnostics: Vec<Diagnostic>,
138 diagnostics: Vec<Diagnostic>,
123 language_server: Option<Arc<helix_lsp::Client>>,
139 language_server: Option<Arc<helix_lsp::Client>>,
@@ -338,6 +354,7 @@ impl Document {
338 let encoding = encoding.unwrap_or(encoding::UTF_8);
354 let encoding = encoding.unwrap_or(encoding::UTF_8);
339 let changes = ChangeSet::new(&text);
355 let changes = ChangeSet::new(&text);
340 let old_state = None;
356 let old_state = None;
357+ let (save_sender, save_receiver) = tokio::sync::mpsc::unbounded_channel();
341
358
342 Self {
359 Self {
343 id: DocumentId::default(),
360 id: DocumentId::default(),
@@ -358,6 +375,9 @@ impl Document {
358 savepoint: None,
375 savepoint: None,
359 last_saved_revision: 0,
376 last_saved_revision: 0,
360 modified_since_accessed: false,
377 modified_since_accessed: false,
378+ save_sender: Some(save_sender),
379+ save_receiver: Some(save_receiver),
380+ current_save: Arc::new(Mutex::new(None)),
361 language_server: None,
381 language_server: None,
362 }
382 }
363 }
383 }
@@ -492,29 +512,34 @@ impl Document {
492 Some(fut.boxed())
512 Some(fut.boxed())
493 }
513 }
494
514
495− pub fn save(&mut self, force: bool) -> impl Future<Output = Result<(), anyhow::Error>> {
515+ pub fn save(&mut self, force: bool) -> Result<(), anyhow::Error> {
496 self.save_impl::<futures_util::future::Ready<_>>(None, force)
516 self.save_impl::<futures_util::future::Ready<_>>(None, force)
497 }
517 }
498
518
499 pub fn format_and_save(
519 pub fn format_and_save(
500 &mut self,
520 &mut self,
521+ formatting: Option<
501− formatting: Option<impl Future<Output = Result<Transaction, FormatterError>>>,
522+ impl Future<Output = Result<Transaction, FormatterError>> + 'static + Send,
523+ >,
502 force: bool,
524 force: bool,
503− ) -> impl Future<Output = anyhow::Result<()>> {
525+ ) -> anyhow::Result<()> {
504 self.save_impl(formatting, force)
526 self.save_impl(formatting, force)
505 }
527 }
506
528
507− // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
529+ // TODO: impl Drop to handle ensuring writes when closed
508− // or is that handled by the OS/async layer
509 /// The `Document`'s text is encoded according to its encoding and written to the file located
530 /// The `Document`'s text is encoded according to its encoding and written to the file located
510 /// at its `path()`.
531 /// at its `path()`.
511 ///
532 ///
512 /// If `formatting` is present, it supplies some changes that we apply to the text before saving.
533 /// If `formatting` is present, it supplies some changes that we apply to the text before saving.
513− fn save_impl<F: Future<Output = Result<Transaction, FormatterError>>>(
534+ fn save_impl<F: Future<Output = Result<Transaction, FormatterError>> + 'static + Send>(
514 &mut self,
535 &mut self,
515 formatting: Option<F>,
536 formatting: Option<F>,
516 force: bool,
537 force: bool,
517− ) -> impl Future<Output = Result<(), anyhow::Error>> {
538+ ) -> Result<(), anyhow::Error> {
539+ if self.save_sender.is_none() {
540+ bail!("saves are closed for this document!");
541+ }
542+
518 // we clone and move text + path into the future so that we asynchronously save the current
543 // we clone and move text + path into the future so that we asynchronously save the current
519 // state without blocking any further edits.
544 // state without blocking any further edits.
520
545
@@ -525,12 +550,13 @@ impl Document {
525 let language_server = self.language_server.clone();
550 let language_server = self.language_server.clone();
526
551
527 // mark changes up to now as saved
552 // mark changes up to now as saved
528− self.reset_modified();
553+ let current_rev = self.get_current_revision();
554+ let doc_id = self.id();
529
555
530 let encoding = self.encoding;
556 let encoding = self.encoding;
531
557
532 // We encode the file according to the `Document`'s encoding.
558 // We encode the file according to the `Document`'s encoding.
533− async move {
559+ let save_event = async move {
534 use tokio::fs::File;
560 use tokio::fs::File;
535 if let Some(parent) = path.parent() {
561 if let Some(parent) = path.parent() {
536 // TODO: display a prompt asking the user if the directories should be created
562 // TODO: display a prompt asking the user if the directories should be created
@@ -563,9 +589,14 @@ impl Document {
563 let mut file = File::create(path).await?;
589 let mut file = File::create(path).await?;
564 to_writer(&mut file, encoding, &text).await?;
590 to_writer(&mut file, encoding, &text).await?;
565
591
592+ let event = DocumentSaveEvent {
593+ revision: current_rev,
594+ doc_id,
595+ };
596+
566 if let Some(language_server) = language_server {
597 if let Some(language_server) = language_server {
567 if !language_server.is_initialized() {
598 if !language_server.is_initialized() {
568− return Ok(());
599+ return Ok(event);
569 }
600 }
570 if let Some(notification) =
601 if let Some(notification) =
571 language_server.text_document_did_save(identifier, &text)
602 language_server.text_document_did_save(identifier, &text)
@@ -574,8 +605,70 @@ impl Document {
574 }
605 }
575 }
606 }
576
607
577− Ok(())
608+ Ok(event)
609+ };
610+
611+ self.save_sender
612+ .as_mut()
613+ .unwrap()
614+ .send(Box::pin(save_event))
615+ .map_err(|err| anyhow!("failed to send save event: {}", err))
616+ }
617+
618+ pub async fn await_save(&mut self) -> Option<DocumentSaveEventResult> {
619+ let mut current_save = self.current_save.lock().await;
620+ if let Some(ref mut save) = *current_save {
621+ let result = save.await;
622+ *current_save = None;
623+ debug!("save of '{:?}' result: {:?}", self.path(), result);
624+ return Some(result);
625+ }
626+
627+ // return early if the receiver is closed
628+ self.save_receiver.as_ref()?;
629+
630+ let save = match self.save_receiver.as_mut().unwrap().recv().await {
631+ Some(save) => save,
632+ None => {
633+ self.save_receiver = None;
634+ return None;
635+ }
636+ };
637+
638+ // save a handle to the future so that when a poll on this
639+ // function gets cancelled, we don't lose it
640+ *current_save = Some(save);
641+ debug!("awaiting save of '{:?}'", self.path());
642+
643+ let result = (*current_save).as_mut().unwrap().await;
644+ *current_save = None;
645+
646+ debug!("save of '{:?}' result: {:?}", self.path(), result);
647+
648+ Some(result)
649+ }
650+
651+ /// Prepares the Document for being closed by stopping any new writes
652+ /// and flushing through the queue of pending writes. If any fail,
653+ /// it stops early before emptying the rest of the queue. Callers
654+ /// should keep calling until it returns None.
655+ pub async fn close(&mut self) -> Option<DocumentSaveEventResult> {
656+ if self.save_sender.is_some() {
657+ self.save_sender = None;
578 }
658 }
659+
660+ let mut final_result = None;
661+
662+ while let Some(save_event) = self.await_save().await {
663+ let is_err = save_event.is_err();
664+ final_result = Some(save_event);
665+
666+ if is_err {
667+ break;
668+ }
669+ }
670+
671+ final_result
579 }
672 }
580
673
581 /// Detect the programming language based on the file type.
674 /// Detect the programming language based on the file type.
@@ -941,6 +1034,19 @@ impl Document {
941 self.last_saved_revision = current_revision;
1034 self.last_saved_revision = current_revision;
942 }
1035 }
943
1036
1037+ /// Set the document's latest saved revision to the given one.
1038+ pub fn set_last_saved_revision(&mut self, rev: usize) {
1039+ self.last_saved_revision = rev;
1040+ }
1041+
1042+ /// Get the current revision number
1043+ pub fn get_current_revision(&mut self) -> usize {
1044+ let history = self.history.take();
1045+ let current_revision = history.current_revision();
1046+ self.history.set(history);
1047+ current_revision
1048+ }
1049+
944 /// Corresponding language scope name. Usually `source.<lang>`.
1050 /// Corresponding language scope name. Usually `source.<lang>`.
945 pub fn language_scope(&self) -> Option<&str> {
1051 pub fn language_scope(&self) -> Option<&str> {
946 self.language
1052 self.language
helix-view/src/editor.rs+68−13
@@ -1,6 +1,6 @@
1use crate::{
1use crate::{
2 clipboard::{get_clipboard_provider, ClipboardProvider},
2 clipboard::{get_clipboard_provider, ClipboardProvider},
3− document::Mode,
3+ document::{DocumentSaveEventResult, Mode},
4 graphics::{CursorKind, Rect},
4 graphics::{CursorKind, Rect},
5 info::Info,
5 info::Info,
6 input::KeyEvent,
6 input::KeyEvent,
@@ -9,8 +9,9 @@ use crate::{
9 Document, DocumentId, View, ViewId,
9 Document, DocumentId, View, ViewId,
10};
10};
11
11
12−use futures_util::future;
13−use futures_util::stream::select_all::SelectAll;
12+use futures_util::stream::{select_all::SelectAll, FuturesUnordered};
13+use futures_util::{future, StreamExt};
14+use helix_lsp::Call;
14use tokio_stream::wrappers::UnboundedReceiverStream;
15use tokio_stream::wrappers::UnboundedReceiverStream;
15
16
16use std::{
17use std::{
@@ -65,7 +66,7 @@ where
65 )
66 )
66}
67}
67
68
68−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
70#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
70pub struct FilePickerConfig {
71pub struct FilePickerConfig {
71 /// IgnoreOptions
72 /// IgnoreOptions
@@ -172,7 +173,7 @@ pub struct Config {
172 pub color_modes: bool,
173 pub color_modes: bool,
173}
174}
174
175
175−#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
176+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
176#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
177#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
177pub struct TerminalConfig {
178pub struct TerminalConfig {
178 pub command: String,
179 pub command: String,
@@ -225,7 +226,7 @@ pub fn get_terminal_provider() -> Option<TerminalConfig> {
225 None
226 None
226}
227}
227
228
228−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
229#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
230#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
230pub struct LspConfig {
231pub struct LspConfig {
231 /// Display LSP progress messages below statusline
232 /// Display LSP progress messages below statusline
@@ -246,7 +247,7 @@ impl Default for LspConfig {
246 }
247 }
247}
248}
248
249
249−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
250#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
251#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
251pub struct SearchConfig {
252pub struct SearchConfig {
252 /// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
253 /// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
@@ -255,7 +256,7 @@ pub struct SearchConfig {
255 pub wrap_around: bool,
256 pub wrap_around: bool,
256}
257}
257
258
258−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
259+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
259#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
260#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
260pub struct StatusLineConfig {
261pub struct StatusLineConfig {
261 pub left: Vec<StatusLineElement>,
262 pub left: Vec<StatusLineElement>,
@@ -279,7 +280,7 @@ impl Default for StatusLineConfig {
279 }
280 }
280}
281}
281
282
282−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
283+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
284#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
284pub struct ModeConfig {
285pub struct ModeConfig {
285 pub normal: String,
286 pub normal: String,
@@ -458,7 +459,7 @@ impl std::str::FromStr for GutterType {
458 }
459 }
459}
460}
460
461
461−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
462#[serde(default)]
463#[serde(default)]
463pub struct WhitespaceConfig {
464pub struct WhitespaceConfig {
464 pub render: WhitespaceRender,
465 pub render: WhitespaceRender,
@@ -688,6 +689,15 @@ pub struct Editor {
688 pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
689 pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
689}
690}
690
691
692+#[derive(Debug)]
693+pub enum EditorEvent {
694+ DocumentSave(DocumentSaveEventResult),
695+ ConfigEvent(ConfigEvent),
696+ LanguageServerMessage((usize, Call)),
697+ DebuggerEvent(dap::Payload),
698+ IdleTimer,
699+}
700+
691#[derive(Debug, Clone)]
701#[derive(Debug, Clone)]
692pub enum ConfigEvent {
702pub enum ConfigEvent {
693 Refresh,
703 Refresh,
@@ -719,6 +729,8 @@ pub enum CloseError {
719 DoesNotExist,
729 DoesNotExist,
720 /// Buffer is modified
730 /// Buffer is modified
721 BufferModified(String),
731 BufferModified(String),
732+ /// Document failed to save
733+ SaveError(anyhow::Error),
722}
734}
723
735
724impl Editor {
736impl Editor {
@@ -1079,8 +1091,12 @@ impl Editor {
1079 self._refresh();
1091 self._refresh();
1080 }
1092 }
1081
1093
1082− pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
1094+ pub async fn close_document(
1095+ &mut self,
1096+ doc_id: DocumentId,
1097+ force: bool,
1098+ ) -> Result<(), CloseError> {
1083− let doc = match self.documents.get(&doc_id) {
1099+ let doc = match self.documents.get_mut(&doc_id) {
1084 Some(doc) => doc,
1100 Some(doc) => doc,
1085 None => return Err(CloseError::DoesNotExist),
1101 None => return Err(CloseError::DoesNotExist),
1086 };
1102 };
@@ -1089,8 +1105,19 @@ impl Editor {
1089 return Err(CloseError::BufferModified(doc.display_name().into_owned()));
1105 return Err(CloseError::BufferModified(doc.display_name().into_owned()));
1090 }
1106 }
1091
1107
1108+ if let Some(Err(err)) = doc.close().await {
1109+ return Err(CloseError::SaveError(err));
1110+ }
1111+
1112+ // Don't fail the whole write because the language server could not
1113+ // acknowledge the close
1092 if let Some(language_server) = doc.language_server() {
1114 if let Some(language_server) = doc.language_server() {
1115+ if let Err(err) = language_server
1093− tokio::spawn(language_server.text_document_did_close(doc.identifier()));
1116+ .text_document_did_close(doc.identifier())
1117+ .await
1118+ {
1119+ log::error!("Error closing doc in language server: {}", err);
1120+ }
1094 }
1121 }
1095
1122
1096 enum Action {
1123 enum Action {
@@ -1269,4 +1296,32 @@ impl Editor {
1269 .await
1296 .await
1270 .map(|_| ())
1297 .map(|_| ())
1271 }
1298 }
1299+
1300+ pub async fn wait_event(&mut self) -> EditorEvent {
1301+ let mut saves: FuturesUnordered<_> = self
1302+ .documents
1303+ .values_mut()
1304+ .map(Document::await_save)
1305+ .collect();
1306+
1307+ tokio::select! {
1308+ biased;
1309+
1310+ Some(Some(event)) = saves.next() => {
1311+ EditorEvent::DocumentSave(event)
1312+ }
1313+ Some(config_event) = self.config_events.1.recv() => {
1314+ EditorEvent::ConfigEvent(config_event)
1315+ }
1316+ Some(message) = self.language_servers.incoming.next() => {
1317+ EditorEvent::LanguageServerMessage(message)
1318+ }
1319+ Some(event) = self.debugger_events.next() => {
1320+ EditorEvent::DebuggerEvent(event)
1321+ }
1322+ _ = &mut self.idle_timer => {
1323+ EditorEvent::IdleTimer
1324+ }
1325+ }
1326+ }
1272}
1327}