@@ -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::{DocumentSavedEventFuture, DocumentSavedEventResult, 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,
12−use futures_util::future;
13use futures_util::stream::select_all::SelectAll;
12use futures_util::stream::select_all::SelectAll;
13+use futures_util::{future, StreamExt};
14use tokio_stream::wrappers::UnboundedReceiverStream;
15use tokio_stream::wrappers::UnboundedReceiverStream;
@@ -28,7 +29,7 @@ use tokio::{
28 time::{sleep, Duration, Instant, Sleep},
29 time::{sleep, Duration, Instant, Sleep},
31−use anyhow::Error;
32+use anyhow::{anyhow, bail, Error};
33pub use helix_core::diagnostic::Severity;
34pub use helix_core::diagnostic::Severity;
34pub use helix_core::register::Registers;
35pub use helix_core::register::Registers;
@@ -65,7 +66,7 @@ where
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,
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> {
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 {
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,
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 {
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 {
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,
@@ -643,12 +644,21 @@ pub struct Breakpoint {
643 pub log_message: Option<String>,
644 pub log_message: Option<String>,
647+use futures_util::stream::{Flatten, Once};
646pub struct Editor {
649pub struct Editor {
647 /// Current editing mode.
650 /// Current editing mode.
648 pub mode: Mode,
651 pub mode: Mode,
649 pub tree: Tree,
652 pub tree: Tree,
650 pub next_document_id: DocumentId,
653 pub next_document_id: DocumentId,
651 pub documents: BTreeMap<DocumentId, Document>,
654 pub documents: BTreeMap<DocumentId, Document>,
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,
652 pub count: Option<std::num::NonZeroUsize>,
662 pub count: Option<std::num::NonZeroUsize>,
653 pub selected_register: Option<char>,
663 pub selected_register: Option<char>,
654 pub registers: Registers,
664 pub registers: Registers,
@@ -688,6 +698,15 @@ pub struct Editor {
688 pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
698 pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
702+pub enum EditorEvent {
703+ DocumentSaved(DocumentSavedEventResult),
704+ ConfigEvent(ConfigEvent),
705+ LanguageServerMessage((usize, Call)),
706+ DebuggerEvent(dap::Payload),
691#[derive(Debug, Clone)]
710#[derive(Debug, Clone)]
692pub enum ConfigEvent {
711pub enum ConfigEvent {
@@ -719,6 +738,8 @@ pub enum CloseError {
719 DoesNotExist,
738 DoesNotExist,
720 /// Buffer is modified
739 /// Buffer is modified
721 BufferModified(String),
740 BufferModified(String),
741+ /// Document failed to save
742+ SaveError(anyhow::Error),
724impl Editor {
745impl Editor {
@@ -739,6 +760,9 @@ impl Editor {
739 tree: Tree::new(area),
760 tree: Tree::new(area),
740 next_document_id: DocumentId::default(),
761 next_document_id: DocumentId::default(),
741 documents: BTreeMap::new(),
762 documents: BTreeMap::new(),
763+ saves: HashMap::new(),
764+ save_queue: SelectAll::new(),
742 count: None,
766 count: None,
743 selected_register: None,
767 selected_register: None,
744 macro_recording: None,
768 macro_recording: None,
@@ -804,12 +828,16 @@ impl Editor {
805 #[inline]
829 #[inline]
806 pub fn set_status<T: Into<Cow<'static, str>>>(&mut self, status: T) {
830 pub fn set_status<T: Into<Cow<'static, str>>>(&mut self, status: T) {
831+ let status = status.into();
832+ log::debug!("editor status: {}", status);
807− self.status_msg = Some((status.into(), Severity::Info));
833+ self.status_msg = Some((status, Severity::Info));
810 #[inline]
836 #[inline]
811 pub fn set_error<T: Into<Cow<'static, str>>>(&mut self, error: T) {
837 pub fn set_error<T: Into<Cow<'static, str>>>(&mut self, error: T) {
838+ let error = error.into();
839+ log::error!("editor error: {}", error);
812− self.status_msg = Some((error.into(), Severity::Error));
840+ self.status_msg = Some((error, Severity::Error));
815 #[inline]
843 #[inline]
@@ -1034,6 +1062,13 @@ impl Editor {
1034 DocumentId(unsafe { NonZeroUsize::new_unchecked(self.next_document_id.0.get() + 1) });
1062 DocumentId(unsafe { NonZeroUsize::new_unchecked(self.next_document_id.0.get() + 1) });
1035 doc.id = id;
1063 doc.id = id;
1036 self.documents.insert(id, doc);
1064 self.documents.insert(id, doc);
1066+ let (save_sender, save_receiver) = tokio::sync::mpsc::unbounded_channel();
1067+ self.saves.insert(id, save_sender);
1069+ let stream = UnboundedReceiverStream::new(save_receiver).flatten();
1070+ self.save_queue.push(stream);
@@ -1080,16 +1115,19 @@ impl Editor {
1082 pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
1117 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) {
1084 Some(doc) => doc,
1119 Some(doc) => doc,
1085 None => return Err(CloseError::DoesNotExist),
1120 None => return Err(CloseError::DoesNotExist),
1088 if !force && doc.is_modified() {
1122 if !force && doc.is_modified() {
1089 return Err(CloseError::BufferModified(doc.display_name().into_owned()));
1123 return Err(CloseError::BufferModified(doc.display_name().into_owned()));
1126+ // This will also disallow any follow-up writes
1127+ self.saves.remove(&doc_id);
1092 if let Some(language_server) = doc.language_server() {
1129 if let Some(language_server) = doc.language_server() {
1130+ // TODO: track error
1093 tokio::spawn(language_server.text_document_did_close(doc.identifier()));
1131 tokio::spawn(language_server.text_document_did_close(doc.identifier()));
@@ -1152,6 +1190,32 @@ impl Editor {
1193+ pub fn save<P: Into<PathBuf>>(
1195+ doc_id: DocumentId,
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
1202+ let path = path.map(|path| path.into());
1203+ let doc = doc_mut!(self, &doc_id);
1204+ let future = doc.save(path, force)?;
1206+ use futures_util::stream;
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))?;
1214+ self.write_count += 1;
1155 pub fn resize(&mut self, area: Rect) {
1219 pub fn resize(&mut self, area: Rect) {
1156 if self.tree.resize(area) {
1220 if self.tree.resize(area) {
1157 self._refresh();
1221 self._refresh();
@@ -1252,14 +1316,14 @@ impl Editor {
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
1256 /// `timeout` parameter to override this.
1320 /// `timeout` parameter to override this.
1257 pub async fn close_language_servers(
1321 pub async fn close_language_servers(
1259 timeout: Option<u64>,
1323 timeout: Option<u64>,
1260 ) -> Result<(), tokio::time::error::Elapsed> {
1324 ) -> Result<(), tokio::time::error::Elapsed> {
1261 tokio::time::timeout(
1325 tokio::time::timeout(
1262− Duration::from_millis(timeout.unwrap_or(500)),
1326+ Duration::from_millis(timeout.unwrap_or(3000)),
1263 future::join_all(
1327 future::join_all(
1264 self.language_servers
1328 self.language_servers
1265 .iter_clients()
1329 .iter_clients()
@@ -1269,4 +1333,48 @@ impl Editor {
1270 .map(|_| ())
1334 .map(|_| ())
1337+ pub async fn wait_event(&mut self) -> EditorEvent {
1341+ Some(event) = self.save_queue.next() => {
1342+ self.write_count -= 1;
1343+ EditorEvent::DocumentSaved(event)
1345+ Some(config_event) = self.config_events.1.recv() => {
1346+ EditorEvent::ConfigEvent(config_event)
1348+ Some(message) = self.language_servers.incoming.next() => {
1349+ EditorEvent::LanguageServerMessage(message)
1351+ Some(event) = self.debugger_events.next() => {
1352+ EditorEvent::DebuggerEvent(event)
1354+ _ = &mut self.idle_timer => {
1355+ EditorEvent::IdleTimer
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;
1365+ let save_event = match save_event {
1366+ Ok(event) => event,
1368+ self.set_error(err.to_string());
1373+ let doc = doc_mut!(self, &save_event.doc_id);
1374+ doc.set_last_saved_revision(save_event.revision);