summaryrefslogtreecommitdiff
path: root/helix-view/src/editor.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-view/src/editor.rs')
-rw-r--r--helix-view/src/editor.rs140
1 files changed, 124 insertions, 16 deletions
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index e9a3c639..cd2b1ad4 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -1,6 +1,6 @@
use crate::{
clipboard::{get_clipboard_provider, ClipboardProvider},
- document::Mode,
+ document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode},
graphics::{CursorKind, Rect},
info::Info,
input::KeyEvent,
@@ -9,8 +9,9 @@ use crate::{
Document, DocumentId, View, ViewId,
};
-use futures_util::future;
use futures_util::stream::select_all::SelectAll;
+use futures_util::{future, StreamExt};
+use helix_lsp::Call;
use tokio_stream::wrappers::UnboundedReceiverStream;
use std::{
@@ -28,7 +29,7 @@ use tokio::{
time::{sleep, Duration, Instant, Sleep},
};
-use anyhow::Error;
+use anyhow::{anyhow, bail, Error};
pub use helix_core::diagnostic::Severity;
pub use helix_core::register::Registers;
@@ -65,7 +66,7 @@ where
)
}
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct FilePickerConfig {
/// IgnoreOptions
@@ -172,7 +173,7 @@ pub struct Config {
pub color_modes: bool,
}
-#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct TerminalConfig {
pub command: String,
@@ -225,7 +226,7 @@ pub fn get_terminal_provider() -> Option<TerminalConfig> {
None
}
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
pub struct LspConfig {
/// Display LSP progress messages below statusline
@@ -246,7 +247,7 @@ impl Default for LspConfig {
}
}
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct SearchConfig {
/// Smart case: Case insensitive searching unless pattern contains upper case characters. Defaults to true.
@@ -255,7 +256,7 @@ pub struct SearchConfig {
pub wrap_around: bool,
}
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct StatusLineConfig {
pub left: Vec<StatusLineElement>,
@@ -279,7 +280,7 @@ impl Default for StatusLineConfig {
}
}
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case", default, deny_unknown_fields)]
pub struct ModeConfig {
pub normal: String,
@@ -458,7 +459,7 @@ impl std::str::FromStr for GutterType {
}
}
-#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct WhitespaceConfig {
pub render: WhitespaceRender,
@@ -643,12 +644,21 @@ pub struct Breakpoint {
pub log_message: Option<String>,
}
+use futures_util::stream::{Flatten, Once};
+
pub struct Editor {
/// Current editing mode.
pub mode: Mode,
pub tree: Tree,
pub next_document_id: DocumentId,
pub documents: BTreeMap<DocumentId, Document>,
+
+ // We Flatten<> to resolve the inner DocumentSavedEventFuture. For that we need a stream of streams, hence the Once<>.
+ // https://stackoverflow.com/a/66875668
+ pub saves: HashMap<DocumentId, UnboundedSender<Once<DocumentSavedEventFuture>>>,
+ pub save_queue: SelectAll<Flatten<UnboundedReceiverStream<Once<DocumentSavedEventFuture>>>>,
+ pub write_count: usize,
+
pub count: Option<std::num::NonZeroUsize>,
pub selected_register: Option<char>,
pub registers: Registers,
@@ -688,6 +698,15 @@ pub struct Editor {
pub config_events: (UnboundedSender<ConfigEvent>, UnboundedReceiver<ConfigEvent>),
}
+#[derive(Debug)]
+pub enum EditorEvent {
+ DocumentSaved(DocumentSavedEventResult),
+ ConfigEvent(ConfigEvent),
+ LanguageServerMessage((usize, Call)),
+ DebuggerEvent(dap::Payload),
+ IdleTimer,
+}
+
#[derive(Debug, Clone)]
pub enum ConfigEvent {
Refresh,
@@ -719,6 +738,8 @@ pub enum CloseError {
DoesNotExist,
/// Buffer is modified
BufferModified(String),
+ /// Document failed to save
+ SaveError(anyhow::Error),
}
impl Editor {
@@ -739,6 +760,9 @@ impl Editor {
tree: Tree::new(area),
next_document_id: DocumentId::default(),
documents: BTreeMap::new(),
+ saves: HashMap::new(),
+ save_queue: SelectAll::new(),
+ write_count: 0,
count: None,
selected_register: None,
macro_recording: None,
@@ -804,12 +828,16 @@ impl Editor {
#[inline]
pub fn set_status<T: Into<Cow<'static, str>>>(&mut self, status: T) {
- self.status_msg = Some((status.into(), Severity::Info));
+ let status = status.into();
+ log::debug!("editor status: {}", status);
+ self.status_msg = Some((status, Severity::Info));
}
#[inline]
pub fn set_error<T: Into<Cow<'static, str>>>(&mut self, error: T) {
- self.status_msg = Some((error.into(), Severity::Error));
+ let error = error.into();
+ log::error!("editor error: {}", error);
+ self.status_msg = Some((error, Severity::Error));
}
#[inline]
@@ -1034,6 +1062,13 @@ impl Editor {
DocumentId(unsafe { NonZeroUsize::new_unchecked(self.next_document_id.0.get() + 1) });
doc.id = id;
self.documents.insert(id, doc);
+
+ let (save_sender, save_receiver) = tokio::sync::mpsc::unbounded_channel();
+ self.saves.insert(id, save_sender);
+
+ let stream = UnboundedReceiverStream::new(save_receiver).flatten();
+ self.save_queue.push(stream);
+
id
}
@@ -1080,16 +1115,19 @@ impl Editor {
}
pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> Result<(), CloseError> {
- let doc = match self.documents.get(&doc_id) {
+ let doc = match self.documents.get_mut(&doc_id) {
Some(doc) => doc,
None => return Err(CloseError::DoesNotExist),
};
-
if !force && doc.is_modified() {
return Err(CloseError::BufferModified(doc.display_name().into_owned()));
}
+ // This will also disallow any follow-up writes
+ self.saves.remove(&doc_id);
+
if let Some(language_server) = doc.language_server() {
+ // TODO: track error
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
}
@@ -1152,6 +1190,32 @@ impl Editor {
Ok(())
}
+ pub fn save<P: Into<PathBuf>>(
+ &mut self,
+ doc_id: DocumentId,
+ path: Option<P>,
+ force: bool,
+ ) -> anyhow::Result<()> {
+ // convert a channel of futures to pipe into main queue one by one
+ // via stream.then() ? then push into main future
+
+ let path = path.map(|path| path.into());
+ let doc = doc_mut!(self, &doc_id);
+ let future = doc.save(path, force)?;
+
+ use futures_util::stream;
+
+ self.saves
+ .get(&doc_id)
+ .ok_or_else(|| anyhow::format_err!("saves are closed for this document!"))?
+ .send(stream::once(Box::pin(future)))
+ .map_err(|err| anyhow!("failed to send save event: {}", err))?;
+
+ self.write_count += 1;
+
+ Ok(())
+ }
+
pub fn resize(&mut self, area: Rect) {
if self.tree.resize(area) {
self._refresh();
@@ -1252,14 +1316,14 @@ impl Editor {
}
}
- /// Closes language servers with timeout. The default timeout is 500 ms, use
+ /// Closes language servers with timeout. The default timeout is 10000 ms, use
/// `timeout` parameter to override this.
pub async fn close_language_servers(
&self,
timeout: Option<u64>,
) -> Result<(), tokio::time::error::Elapsed> {
tokio::time::timeout(
- Duration::from_millis(timeout.unwrap_or(500)),
+ Duration::from_millis(timeout.unwrap_or(3000)),
future::join_all(
self.language_servers
.iter_clients()
@@ -1269,4 +1333,48 @@ impl Editor {
.await
.map(|_| ())
}
+
+ pub async fn wait_event(&mut self) -> EditorEvent {
+ tokio::select! {
+ biased;
+
+ Some(event) = self.save_queue.next() => {
+ self.write_count -= 1;
+ EditorEvent::DocumentSaved(event)
+ }
+ Some(config_event) = self.config_events.1.recv() => {
+ EditorEvent::ConfigEvent(config_event)
+ }
+ Some(message) = self.language_servers.incoming.next() => {
+ EditorEvent::LanguageServerMessage(message)
+ }
+ Some(event) = self.debugger_events.next() => {
+ EditorEvent::DebuggerEvent(event)
+ }
+ _ = &mut self.idle_timer => {
+ EditorEvent::IdleTimer
+ }
+ }
+ }
+
+ pub async fn flush_writes(&mut self) -> anyhow::Result<()> {
+ while self.write_count > 0 {
+ if let Some(save_event) = self.save_queue.next().await {
+ self.write_count -= 1;
+
+ let save_event = match save_event {
+ Ok(event) => event,
+ Err(err) => {
+ self.set_error(err.to_string());
+ bail!(err);
+ }
+ };
+
+ let doc = doc_mut!(self, &save_event.doc_id);
+ doc.set_last_saved_revision(save_event.revision);
+ }
+ }
+
+ Ok(())
+ }
}