on master help sign in
chore(write): serialize write operations within a Document

The way that document writes are handled are by submitting them to the async job pool, which are all executed opportunistically out of order. It was discovered that this can lead to write inconsistencies when there are multiple writes to the same file in quick succession. This seeks to fix this problem by removing document writes from the general pool of jobs and into its own specialized event. Now when a user submits a write with one of the write commands, a request is simply queued up in a new mpsc channel that each Document makes to handle its own writes. This way, if multiple writes are submitted on the same document, they are executed in order, while still allowing concurrent writes for different documents.

commitd706194597d462fbaeb1ef55e2e8fb6eae38d2f3 authorSkyler Hawthorne <skyler@dead10ck.com> dateSun 10 Apr 2022, 11:05 (5 years ago) parentfaf0c52
6 files changed+297−64
helix-term/src/application.rs+103−32 helix-term/src/commands.rs+1−1 helix-term/src/commands/typed.rs+8−5 helix-term/tests/test/write.rs−2 helix-view/src/document.rs+117−11 helix-view/src/editor.rs+68−13
helix-term/src/application.rs+103−32
@@ -6,7 +6,14 @@ use helix_core::{
66 pos_at_coords, syntax, Selection,
77};
88use 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+};
1017use serde_json::json;
1118
1219use crate::{
@@ -19,7 +26,7 @@ use crate::{
1926 ui::{self, overlay::overlayed},
2027};
2128
22−use log::{error, warn};
29+use log::{debug, error, warn};
2330use std::{
2431 io::{stdin, stdout, Write},
2532 sync::Arc,
@@ -294,26 +301,6 @@ impl Application {
294301 Some(signal) = self.signals.next() => {
295302 self.handle_signals(signal).await;
296303 }
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− }
317304 Some(callback) = self.jobs.futures.next() => {
318305 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
319306 self.render();
@@ -322,20 +309,47 @@ impl Application {
322309 self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
323310 self.render();
324311 }
325− _ = &mut self.editor.idle_timer => {
326− // idle timeout
327− self.editor.clear_idle_timer();
328− self.handle_idle_timeout();
312+ event = self.editor.wait_event() => {
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 => {
339+ self.editor.clear_idle_timer();
340+ self.handle_idle_timeout();
329341
330− #[cfg(feature = "integration")]
331− {
332− idle_handled = true;
342+ #[cfg(feature = "integration")]
343+ {
344+ idle_handled = true;
345+ }
346+ }
333347 }
334348 }
335349 }
336350
337351 // 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
339353 #[cfg(feature = "integration")]
340354 {
341355 if idle_handled {
@@ -446,6 +460,46 @@ impl Application {
446460 }
447461 }
448462
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+
449503 pub fn handle_terminal_events(&mut self, event: Result<CrosstermEvent, crossterm::ErrorKind>) {
450504 let mut cx = crate::compositor::Context {
451505 editor: &mut self.editor,
@@ -866,11 +920,28 @@ impl Application {
866920
867921 self.event_loop(input_stream).await;
868922
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+ }
870935
936+ let close_err = self.close().await.err();
871937 restore_term()?;
872938
873− if let Some(err) = err {
939+ for (path, err) in save_errs {
940+ self.editor.exit_code = 1;
941+ eprintln!("Error closing '{}': {}", path, err);
942+ }
943+
944+ if let Some(err) = close_err {
874945 self.editor.exit_code = 1;
875946 eprintln!("Error: {}", err);
876947 }
helix-term/src/commands.rs+1−1
@@ -51,7 +51,7 @@ use crate::{
5151 ui::{self, overlay::overlayed, FilePicker, Picker, Popup, Prompt, PromptEvent},
5252};
5353
54−use crate::job::{self, Job, Jobs};
54+use crate::job::{self, Jobs};
5555use futures_util::{FutureExt, StreamExt};
5656use std::{collections::HashMap, fmt, future::Future};
5757use std::{collections::HashSet, num::NonZeroUsize};
helix-term/src/commands/typed.rs+8−5
@@ -77,7 +77,9 @@ fn buffer_close_by_ids_impl(
7777 let (modified_ids, modified_names): (Vec<_>, Vec<_>) = doc_ids
7878 .iter()
7979 .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+ {
8183 Some((doc_id, name))
8284 } else {
8385 None
@@ -269,6 +271,7 @@ fn write_impl(
269271 doc.set_path(Some(path.as_ref().as_ref()))
270272 .context("invalid filepath")?;
271273 }
274+
272275 if doc.path().is_none() {
273276 bail!("cannot write a buffer without a filename");
274277 }
@@ -287,8 +290,8 @@ fn write_impl(
287290 } else {
288291 None
289292 };
290− let future = doc.format_and_save(fmt, force);
291− cx.jobs.add(Job::new(future).wait_before_exiting());
293+
294+ doc.format_and_save(fmt, force)?;
292295
293296 if path.is_some() {
294297 let id = doc.id();
@@ -602,8 +605,8 @@ fn write_all_impl(
602605 } else {
603606 None
604607 };
605− let future = doc.format_and_save(fmt, force);
606− jobs.add(Job::new(future).wait_before_exiting());
608+
609+ doc.format_and_save(fmt, force)?;
607610 }
608611
609612 if quit {
helix-term/tests/test/write.rs−2
@@ -62,7 +62,6 @@ async fn test_write_quit() -> anyhow::Result<()> {
6262}
6363
6464#[tokio::test]
65−#[ignore]
6665async fn test_write_concurrent() -> anyhow::Result<()> {
6766 let mut file = tempfile::NamedTempFile::new()?;
6867 let mut command = String::new();
@@ -92,7 +91,6 @@ async fn test_write_concurrent() -> anyhow::Result<()> {
9291}
9392
9493#[tokio::test]
95−#[ignore]
9694async fn test_write_fail_mod_flag() -> anyhow::Result<()> {
9795 let file = helpers::new_readonly_tempfile()?;
9896
helix-view/src/document.rs+117−11
@@ -3,6 +3,7 @@ use futures_util::future::BoxFuture;
33use futures_util::FutureExt;
44use helix_core::auto_pairs::AutoPairs;
55use helix_core::Range;
6+use log::debug;
67use serde::de::{self, Deserialize, Deserializer};
78use serde::Serialize;
89use std::borrow::Cow;
@@ -13,6 +14,8 @@ use std::future::Future;
1314use std::path::{Path, PathBuf};
1415use std::str::FromStr;
1516use std::sync::Arc;
17+use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
18+use tokio::sync::Mutex;
1619
1720use helix_core::{
1821 encoding,
@@ -83,6 +86,16 @@ impl Serialize for Mode {
8386 }
8487}
8588
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+
8699pub struct Document {
87100 pub(crate) id: DocumentId,
88101 text: Rope,
@@ -118,6 +131,9 @@ pub struct Document {
118131 last_saved_revision: usize,
119132 version: i32, // should be usize?
120133 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>>>,
121137
122138 diagnostics: Vec<Diagnostic>,
123139 language_server: Option<Arc<helix_lsp::Client>>,
@@ -338,6 +354,7 @@ impl Document {
338354 let encoding = encoding.unwrap_or(encoding::UTF_8);
339355 let changes = ChangeSet::new(&text);
340356 let old_state = None;
357+ let (save_sender, save_receiver) = tokio::sync::mpsc::unbounded_channel();
341358
342359 Self {
343360 id: DocumentId::default(),
@@ -358,6 +375,9 @@ impl Document {
358375 savepoint: None,
359376 last_saved_revision: 0,
360377 modified_since_accessed: false,
378+ save_sender: Some(save_sender),
379+ save_receiver: Some(save_receiver),
380+ current_save: Arc::new(Mutex::new(None)),
361381 language_server: None,
362382 }
363383 }
@@ -492,29 +512,34 @@ impl Document {
492512 Some(fut.boxed())
493513 }
494514
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> {
496516 self.save_impl::<futures_util::future::Ready<_>>(None, force)
497517 }
498518
499519 pub fn format_and_save(
500520 &mut self,
501− formatting: Option<impl Future<Output = Result<Transaction, FormatterError>>>,
521+ formatting: Option<
522+ impl Future<Output = Result<Transaction, FormatterError>> + 'static + Send,
523+ >,
502524 force: bool,
503− ) -> impl Future<Output = anyhow::Result<()>> {
525+ ) -> anyhow::Result<()> {
504526 self.save_impl(formatting, force)
505527 }
506528
507− // TODO: do we need some way of ensuring two save operations on the same doc can't run at once?
508− // or is that handled by the OS/async layer
529+ // TODO: impl Drop to handle ensuring writes when closed
509530 /// The `Document`'s text is encoded according to its encoding and written to the file located
510531 /// at its `path()`.
511532 ///
512533 /// 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>(
514535 &mut self,
515536 formatting: Option<F>,
516537 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+
518543 // we clone and move text + path into the future so that we asynchronously save the current
519544 // state without blocking any further edits.
520545
@@ -525,12 +550,13 @@ impl Document {
525550 let language_server = self.language_server.clone();
526551
527552 // 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();
529555
530556 let encoding = self.encoding;
531557
532558 // We encode the file according to the `Document`'s encoding.
533− async move {
559+ let save_event = async move {
534560 use tokio::fs::File;
535561 if let Some(parent) = path.parent() {
536562 // TODO: display a prompt asking the user if the directories should be created
@@ -563,9 +589,14 @@ impl Document {
563589 let mut file = File::create(path).await?;
564590 to_writer(&mut file, encoding, &text).await?;
565591
592+ let event = DocumentSaveEvent {
593+ revision: current_rev,
594+ doc_id,
595+ };
596+
566597 if let Some(language_server) = language_server {
567598 if !language_server.is_initialized() {
568− return Ok(());
599+ return Ok(event);
569600 }
570601 if let Some(notification) =
571602 language_server.text_document_did_save(identifier, &text)
@@ -574,8 +605,70 @@ impl Document {
574605 }
575606 }
576607
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;
578658 }
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
579672 }
580673
581674 /// Detect the programming language based on the file type.
@@ -941,6 +1034,19 @@ impl Document {
9411034 self.last_saved_revision = current_revision;
9421035 }
9431036
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+
9441050 /// Corresponding language scope name. Usually `source.<lang>`.
9451051 pub fn language_scope(&self) -> Option<&str> {
9461052 self.language
helix-view/src/editor.rs+68−13
@@ -1,6 +1,6 @@
11use crate::{
22 clipboard::{get_clipboard_provider, ClipboardProvider},
3− document::Mode,
3+ document::{DocumentSaveEventResult, Mode},
44 graphics::{CursorKind, Rect},
55 info::Info,
66 input::KeyEvent,
@@ -9,8 +9,9 @@ use crate::{
99 Document, DocumentId, View, ViewId,
1010};
1111
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;
1415use tokio_stream::wrappers::UnboundedReceiverStream;
1516
1617use std::{
@@ -65,7 +66,7 @@ where
6566 )
6667}
6768
68−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
69+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6970#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
7071pub struct FilePickerConfig {
7172 /// IgnoreOptions
@@ -172,7 +173,7 @@ pub struct Config {
172173 pub color_modes: bool,
173174}
174175
175−#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
176+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
176177#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
177178pub struct TerminalConfig {
178179 pub command: String,
@@ -225,7 +226,7 @@ pub fn get_terminal_provider() -> Option<TerminalConfig> {
225226 None
226227}
227228
228−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
229230#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
230231pub struct LspConfig {
231232 /// Display LSP progress messages below statusline
@@ -246,7 +247,7 @@ impl Default for LspConfig {
246247 }
247248}
248249
249−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
250+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
250251#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
251252pub struct SearchConfig {
252253 /// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
@@ -255,7 +256,7 @@ pub struct SearchConfig {
255256 pub wrap_around: bool,
256257}
257258
258−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
259+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
259260#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
260261pub struct StatusLineConfig {
261262 pub left: Vec<StatusLineElement>,
@@ -279,7 +280,7 @@ impl Default for StatusLineConfig {
279280 }
280281}
281282
282−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
283+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283284#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
284285pub struct ModeConfig {
285286 pub normal: String,
@@ -458,7 +459,7 @@ impl std::str::FromStr for GutterType {
458459 }
459460}
460461
461−#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
462463#[serde(default)]
463464pub struct WhitespaceConfig {
464465 pub render: WhitespaceRender,
@@ -688,6 +689,15 @@ pub struct Editor {
688689 pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
689690}
690691
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+
691701#[derive(Debug, Clone)]
692702pub enum ConfigEvent {
693703 Refresh,
@@ -719,6 +729,8 @@ pub enum CloseError {
719729 DoesNotExist,
720730 /// Buffer is modified
721731 BufferModified(String),
732+ /// Document failed to save
733+ SaveError(anyhow::Error),
722734}
723735
724736impl Editor {
@@ -1079,8 +1091,12 @@ impl Editor {
10791091 self._refresh();
10801092 }
10811093
1082− pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
1083− let doc = match self.documents.get(&doc_id) {
1094+ pub async fn close_document(
1095+ &mut self,
1096+ doc_id: DocumentId,
1097+ force: bool,
1098+ ) -> Result<(), CloseError> {
1099+ let doc = match self.documents.get_mut(&doc_id) {
10841100 Some(doc) => doc,
10851101 None => return Err(CloseError::DoesNotExist),
10861102 };
@@ -1089,8 +1105,19 @@ impl Editor {
10891105 return Err(CloseError::BufferModified(doc.display_name().into_owned()));
10901106 }
10911107
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
10921114 if let Some(language_server) = doc.language_server() {
1093− tokio::spawn(language_server.text_document_did_close(doc.identifier()));
1115+ if let Err(err) = language_server
1116+ .text_document_did_close(doc.identifier())
1117+ .await
1118+ {
1119+ log::error!("Error closing doc in language server: {}", err);
1120+ }
10941121 }
10951122
10961123 enum Action {
@@ -1269,4 +1296,32 @@ impl Editor {
12691296 .await
12701297 .map(|_| ())
12711298 }
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+ }
12721327}