@@ -1,6 +1,6 @@
11use crate::{
22 clipboard::{get_clipboard_provider, ClipboardProvider},
3− document::Mode,
3+ document::{DocumentSavedEventFuture, DocumentSavedEventResult, 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;
1312use futures_util::stream::select_all::SelectAll;
13+use futures_util::{future, StreamExt};
14+use helix_lsp::Call;
1415use tokio_stream::wrappers::UnboundedReceiverStream;
1516
1617use std::{
@@ -28,7 +29,7 @@ use tokio::{
2829 time::{sleep, Duration, Instant, Sleep},
2930};
3031
31−use anyhow::Error;
32+use anyhow::{anyhow, bail, Error};
3233
3334pub use helix_core::diagnostic::Severity;
3435pub use helix_core::register::Registers;
@@ -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,
@@ -643,12 +644,21 @@ pub struct Breakpoint {
643644 pub log_message: Option<String>,
644645}
645646
647+use futures_util::stream::{Flatten, Once};
648+
646649pub struct Editor {
647650 /// Current editing mode.
648651 pub mode: Mode,
649652 pub tree: Tree,
650653 pub next_document_id: DocumentId,
651654 pub documents: BTreeMap<DocumentId, Document>,
655+
656+ // We Flatten<> to resolve the inner DocumentSavedEventFuture. For that we need a stream of streams, hence the Once<>.
657+ // https://stackoverflow.com/a/66875668
658+ pub saves: HashMap<DocumentId, UnboundedSender<Once<DocumentSavedEventFuture>>>,
659+ pub save_queue: SelectAll<Flatten<UnboundedReceiverStream<Once<DocumentSavedEventFuture>>>>,
660+ pub write_count: usize,
661+
652662 pub count: Option<std::num::NonZeroUsize>,
653663 pub selected_register: Option<char>,
654664 pub registers: Registers,
@@ -688,6 +698,15 @@ pub struct Editor {
688698 pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
689699}
690700
701+#[derive(Debug)]
702+pub enum EditorEvent {
703+ DocumentSaved(DocumentSavedEventResult),
704+ ConfigEvent(ConfigEvent),
705+ LanguageServerMessage((usize, Call)),
706+ DebuggerEvent(dap::Payload),
707+ IdleTimer,
708+}
709+
691710#[derive(Debug, Clone)]
692711pub enum ConfigEvent {
693712 Refresh,
@@ -719,6 +738,8 @@ pub enum CloseError {
719738 DoesNotExist,
720739 /// Buffer is modified
721740 BufferModified(String),
741+ /// Document failed to save
742+ SaveError(anyhow::Error),
722743}
723744
724745impl Editor {
@@ -739,6 +760,9 @@ impl Editor {
739760 tree: Tree::new(area),
740761 next_document_id: DocumentId::default(),
741762 documents: BTreeMap::new(),
763+ saves: HashMap::new(),
764+ save_queue: SelectAll::new(),
765+ write_count: 0,
742766 count: None,
743767 selected_register: None,
744768 macro_recording: None,
@@ -804,12 +828,16 @@ impl Editor {
804828
805829 #[inline]
806830 pub fn set_status<T: Into<Cow<'static, str>>>(&mut self, status: T) {
807− self.status_msg = Some((status.into(), Severity::Info));
831+ let status = status.into();
832+ log::debug!("editor status: {}", status);
833+ self.status_msg = Some((status, Severity::Info));
808834 }
809835
810836 #[inline]
811837 pub fn set_error<T: Into<Cow<'static, str>>>(&mut self, error: T) {
812− self.status_msg = Some((error.into(), Severity::Error));
838+ let error = error.into();
839+ log::error!("editor error: {}", error);
840+ self.status_msg = Some((error, Severity::Error));
813841 }
814842
815843 #[inline]
@@ -1034,6 +1062,13 @@ impl Editor {
10341062 DocumentId(unsafe { NonZeroUsize::new_unchecked(self.next_document_id.0.get() + 1) });
10351063 doc.id = id;
10361064 self.documents.insert(id, doc);
1065+
1066+ let (save_sender, save_receiver) = tokio::sync::mpsc::unbounded_channel();
1067+ self.saves.insert(id, save_sender);
1068+
1069+ let stream = UnboundedReceiverStream::new(save_receiver).flatten();
1070+ self.save_queue.push(stream);
1071+
10371072 id
10381073 }
10391074
@@ -1080,16 +1115,19 @@ impl Editor {
10801115 }
10811116
10821117 pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
1083− let doc = match self.documents.get(&doc_id) {
1118+ let doc = match self.documents.get_mut(&doc_id) {
10841119 Some(doc) => doc,
10851120 None => return Err(CloseError::DoesNotExist),
10861121 };
1087−
10881122 if !force && doc.is_modified() {
10891123 return Err(CloseError::BufferModified(doc.display_name().into_owned()));
10901124 }
10911125
1126+ // This will also disallow any follow-up writes
1127+ self.saves.remove(&doc_id);
1128+
10921129 if let Some(language_server) = doc.language_server() {
1130+ // TODO: track error
10931131 tokio::spawn(language_server.text_document_did_close(doc.identifier()));
10941132 }
10951133
@@ -1152,6 +1190,32 @@ impl Editor {
11521190 Ok(())
11531191 }
11541192
1193+ pub fn save<P: Into<PathBuf>>(
1194+ &mut self,
1195+ doc_id: DocumentId,
1196+ path: Option<P>,
1197+ force: bool,
1198+ ) -> anyhow::Result<()> {
1199+ // convert a channel of futures to pipe into main queue one by one
1200+ // via stream.then() ? then push into main future
1201+
1202+ let path = path.map(|path| path.into());
1203+ let doc = doc_mut!(self, &doc_id);
1204+ let future = doc.save(path, force)?;
1205+
1206+ use futures_util::stream;
1207+
1208+ self.saves
1209+ .get(&doc_id)
1210+ .ok_or_else(|| anyhow::format_err!("saves are closed for this document!"))?
1211+ .send(stream::once(Box::pin(future)))
1212+ .map_err(|err| anyhow!("failed to send save event: {}", err))?;
1213+
1214+ self.write_count += 1;
1215+
1216+ Ok(())
1217+ }
1218+
11551219 pub fn resize(&mut self, area: Rect) {
11561220 if self.tree.resize(area) {
11571221 self._refresh();
@@ -1252,14 +1316,14 @@ impl Editor {
12521316 }
12531317 }
12541318
1255− /// Closes language servers with timeout. The default timeout is 500 ms, use
1319+ /// Closes language servers with timeout. The default timeout is 10000 ms, use
12561320 /// `timeout` parameter to override this.
12571321 pub async fn close_language_servers(
12581322 &self,
12591323 timeout: Option<u64>,
12601324 ) -> Result<(), tokio::time::error::Elapsed> {
12611325 tokio::time::timeout(
1262− Duration::from_millis(timeout.unwrap_or(500)),
1326+ Duration::from_millis(timeout.unwrap_or(3000)),
12631327 future::join_all(
12641328 self.language_servers
12651329 .iter_clients()
@@ -1269,4 +1333,48 @@ impl Editor {
12691333 .await
12701334 .map(|_| ())
12711335 }
1336+
1337+ pub async fn wait_event(&mut self) -> EditorEvent {
1338+ tokio::select! {
1339+ biased;
1340+
1341+ Some(event) = self.save_queue.next() => {
1342+ self.write_count -= 1;
1343+ EditorEvent::DocumentSaved(event)
1344+ }
1345+ Some(config_event) = self.config_events.1.recv() => {
1346+ EditorEvent::ConfigEvent(config_event)
1347+ }
1348+ Some(message) = self.language_servers.incoming.next() => {
1349+ EditorEvent::LanguageServerMessage(message)
1350+ }
1351+ Some(event) = self.debugger_events.next() => {
1352+ EditorEvent::DebuggerEvent(event)
1353+ }
1354+ _ = &mut self.idle_timer => {
1355+ EditorEvent::IdleTimer
1356+ }
1357+ }
1358+ }
1359+
1360+ pub async fn flush_writes(&mut self) -> anyhow::Result<()> {
1361+ while self.write_count > 0 {
1362+ if let Some(save_event) = self.save_queue.next().await {
1363+ self.write_count -= 1;
1364+
1365+ let save_event = match save_event {
1366+ Ok(event) => event,
1367+ Err(err) => {
1368+ self.set_error(err.to_string());
1369+ bail!(err);
1370+ }
1371+ };
1372+
1373+ let doc = doc_mut!(self, &save_event.doc_id);
1374+ doc.set_last_saved_revision(save_event.revision);
1375+ }
1376+ }
1377+
1378+ Ok(())
1379+ }
12721380}