diff options
Diffstat (limited to 'helix-term/src/application.rs')
-rw-r--r-- | helix-term/src/application.rs | 208 |
1 files changed, 156 insertions, 52 deletions
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 4bb36b59..b4b4a675 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -1,12 +1,19 @@ use arc_swap::{access::Map, ArcSwap}; use futures_util::Stream; use helix_core::{ - config::{default_syntax_loader, user_syntax_loader}, diagnostic::{DiagnosticTag, NumberOrString}, + path::get_relative_path, pos_at_coords, syntax, Selection, }; use helix_lsp::{lsp, util::lsp_pos_to_pos, LspProgressMap}; -use helix_view::{align_view, editor::ConfigEvent, theme, tree::Layout, Align, Editor}; +use helix_view::{ + align_view, + document::DocumentSavedEventResult, + editor::{ConfigEvent, EditorEvent}, + theme, + tree::Layout, + Align, Editor, +}; use serde_json::json; use crate::{ @@ -19,7 +26,7 @@ use crate::{ ui::{self, overlay::overlayed}, }; -use log::{error, warn}; +use log::{debug, error, warn}; use std::{ io::{stdin, stdout, Write}, sync::Arc, @@ -102,7 +109,11 @@ fn restore_term() -> Result<(), Error> { } impl Application { - pub fn new(args: Args, config: Config) -> Result<Self, Error> { + pub fn new( + args: Args, + config: Config, + syn_loader_conf: syntax::Configuration, + ) -> Result<Self, Error> { #[cfg(feature = "integration")] setup_integration_logging(); @@ -129,14 +140,6 @@ impl Application { }) .unwrap_or_else(|| theme_loader.default_theme(true_color)); - let syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| { - eprintln!("Bad language config: {}", err); - eprintln!("Press <ENTER> to continue with default language config"); - use std::io::Read; - // This waits for an enter press. - let _ = std::io::stdin().read(&mut []); - default_syntax_loader() - }); let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf)); let mut compositor = Compositor::new().context("build compositor")?; @@ -245,6 +248,10 @@ impl Application { Ok(app) } + #[cfg(feature = "integration")] + fn render(&mut self) {} + + #[cfg(not(feature = "integration"))] fn render(&mut self) { let compositor = &mut self.compositor; @@ -275,9 +282,6 @@ impl Application { where S: Stream<Item = crossterm::Result<crossterm::event::Event>> + Unpin, { - #[cfg(feature = "integration")] - let mut idle_handled = false; - loop { if self.editor.should_close() { return false; @@ -294,26 +298,6 @@ impl Application { Some(signal) = self.signals.next() => { self.handle_signals(signal).await; } - Some((id, call)) = self.editor.language_servers.incoming.next() => { - self.handle_language_server_message(call, id).await; - // limit render calls for fast language server messages - let last = self.editor.language_servers.incoming.is_empty(); - - if last || self.last_render.elapsed() > LSP_DEADLINE { - self.render(); - self.last_render = Instant::now(); - } - } - Some(payload) = self.editor.debugger_events.next() => { - let needs_render = self.editor.handle_debugger_message(payload).await; - if needs_render { - self.render(); - } - } - Some(config_event) = self.editor.config_events.1.recv() => { - self.handle_config_events(config_event); - self.render(); - } Some(callback) = self.jobs.futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render(); @@ -322,26 +306,22 @@ impl Application { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render(); } - _ = &mut self.editor.idle_timer => { - // idle timeout - self.editor.clear_idle_timer(); - self.handle_idle_timeout(); + event = self.editor.wait_event() => { + let _idle_handled = self.handle_editor_event(event).await; #[cfg(feature = "integration")] { - idle_handled = true; + if _idle_handled { + return true; + } } } } // for integration tests only, reset the idle timer after every - // event to make a signal when test events are done processing + // event to signal when test events are done processing #[cfg(feature = "integration")] { - if idle_handled { - return true; - } - self.editor.reset_idle_timer(); } } @@ -446,6 +426,111 @@ impl Application { } } + pub fn handle_document_write(&mut self, doc_save_event: DocumentSavedEventResult) { + let doc_save_event = match doc_save_event { + Ok(event) => event, + Err(err) => { + self.editor.set_error(err.to_string()); + return; + } + }; + + let doc = match self.editor.document_mut(doc_save_event.doc_id) { + None => { + warn!( + "received document saved event for non-existent doc id: {}", + doc_save_event.doc_id + ); + + return; + } + Some(doc) => doc, + }; + + debug!( + "document {:?} saved with revision {}", + doc.path(), + doc_save_event.revision + ); + + doc.set_last_saved_revision(doc_save_event.revision); + + let lines = doc_save_event.text.len_lines(); + let bytes = doc_save_event.text.len_bytes(); + + if doc.path() != Some(&doc_save_event.path) { + if let Err(err) = doc.set_path(Some(&doc_save_event.path)) { + log::error!( + "error setting path for doc '{:?}': {}", + doc.path(), + err.to_string(), + ); + + self.editor.set_error(err.to_string()); + return; + } + + let loader = self.editor.syn_loader.clone(); + + // borrowing the same doc again to get around the borrow checker + let doc = doc_mut!(self.editor, &doc_save_event.doc_id); + let id = doc.id(); + doc.detect_language(loader); + let _ = self.editor.refresh_language_server(id); + } + + // TODO: fix being overwritten by lsp + self.editor.set_status(format!( + "'{}' written, {}L {}B", + get_relative_path(&doc_save_event.path).to_string_lossy(), + lines, + bytes + )); + } + + #[inline(always)] + pub async fn handle_editor_event(&mut self, event: EditorEvent) -> bool { + log::debug!("received editor event: {:?}", event); + + match event { + EditorEvent::DocumentSaved(event) => { + self.handle_document_write(event); + self.render(); + } + EditorEvent::ConfigEvent(event) => { + self.handle_config_events(event); + self.render(); + } + EditorEvent::LanguageServerMessage((id, call)) => { + self.handle_language_server_message(call, id).await; + // limit render calls for fast language server messages + let last = self.editor.language_servers.incoming.is_empty(); + + if last || self.last_render.elapsed() > LSP_DEADLINE { + self.render(); + self.last_render = Instant::now(); + } + } + EditorEvent::DebuggerEvent(payload) => { + let needs_render = self.editor.handle_debugger_message(payload).await; + if needs_render { + self.render(); + } + } + EditorEvent::IdleTimer => { + self.editor.clear_idle_timer(); + self.handle_idle_timeout(); + + #[cfg(feature = "integration")] + { + return true; + } + } + } + + false + } + pub fn handle_terminal_events(&mut self, event: Result<CrosstermEvent, crossterm::ErrorKind>) { let mut cx = crate::compositor::Context { editor: &mut self.editor, @@ -866,11 +951,10 @@ impl Application { self.event_loop(input_stream).await; - let err = self.close().await.err(); - + let close_errs = self.close().await; restore_term()?; - if let Some(err) = err { + for err in close_errs { self.editor.exit_code = 1; eprintln!("Error: {}", err); } @@ -878,13 +962,33 @@ impl Application { Ok(self.editor.exit_code) } - pub async fn close(&mut self) -> anyhow::Result<()> { - self.jobs.finish().await?; + pub async fn close(&mut self) -> Vec<anyhow::Error> { + // [NOTE] we intentionally do not return early for errors because we + // want to try to run as much cleanup as we can, regardless of + // errors along the way + let mut errs = Vec::new(); + + if let Err(err) = self + .jobs + .finish(&mut self.editor, Some(&mut self.compositor)) + .await + { + log::error!("Error executing job: {}", err); + errs.push(err); + }; + + if let Err(err) = self.editor.flush_writes().await { + log::error!("Error writing: {}", err); + errs.push(err); + } if self.editor.close_language_servers(None).await.is_err() { log::error!("Timed out waiting for language servers to shutdown"); - }; + errs.push(anyhow::format_err!( + "Timed out waiting for language servers to shutdown" + )); + } - Ok(()) + errs } } |