Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/application.rs')
| -rw-r--r-- | helix-term/src/application.rs | 279 |
1 files changed, 78 insertions, 201 deletions
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 8c1db649..40a03e2d 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -30,41 +30,32 @@ use crate::{ }; use log::{debug, error, info, warn}; -use std::{ - io::{stdin, IsTerminal}, - path::Path, - sync::Arc, -}; +#[cfg(not(feature = "integration"))] +use std::io::stdout; +use std::{io::stdin, path::Path, sync::Arc}; -#[cfg_attr(windows, allow(unused_imports))] -use anyhow::{Context, Error}; +#[cfg(not(windows))] +use anyhow::Context; +use anyhow::Error; +use crossterm::{event::Event as CrosstermEvent, tty::IsTty}; #[cfg(not(windows))] use {signal_hook::consts::signal, signal_hook_tokio::Signals}; #[cfg(windows)] type Signals = futures_util::stream::Empty<()>; -#[cfg(all(not(windows), not(feature = "integration")))] -use tui::backend::TerminaBackend; - -#[cfg(all(windows, not(feature = "integration")))] +#[cfg(not(feature = "integration"))] use tui::backend::CrosstermBackend; #[cfg(feature = "integration")] use tui::backend::TestBackend; -#[cfg(all(not(windows), not(feature = "integration")))] -type TerminalBackend = TerminaBackend; -#[cfg(all(windows, not(feature = "integration")))] +#[cfg(not(feature = "integration"))] type TerminalBackend = CrosstermBackend<std::io::Stdout>; + #[cfg(feature = "integration")] type TerminalBackend = TestBackend; -#[cfg(not(windows))] -type TerminalEvent = termina::Event; -#[cfg(windows)] -type TerminalEvent = crossterm::event::Event; - type Terminal = tui::terminal::Terminal<TerminalBackend>; pub struct Application { @@ -77,8 +68,6 @@ pub struct Application { signals: Signals, jobs: Jobs, lsp_progress: LspProgressMap, - - theme_mode: Option<theme::Mode>, } #[cfg(feature = "integration")] @@ -114,18 +103,14 @@ impl Application { theme_parent_dirs.extend(helix_loader::runtime_dirs().iter().cloned()); let theme_loader = theme::Loader::new(&theme_parent_dirs); - #[cfg(all(not(windows), not(feature = "integration")))] - let backend = TerminaBackend::new((&config.editor).into()) - .context("failed to create terminal backend")?; - #[cfg(all(windows, not(feature = "integration")))] - let backend = CrosstermBackend::new(std::io::stdout(), (&config.editor).into()); + #[cfg(not(feature = "integration"))] + let backend = CrosstermBackend::new(stdout(), &config.editor); #[cfg(feature = "integration")] let backend = TestBackend::new(120, 150); - let theme_mode = backend.get_theme_mode(); let terminal = Terminal::new(backend)?; - let area = terminal.size(); + let area = terminal.size().expect("couldn't get terminal size"); let mut compositor = Compositor::new(area); let config = Arc::new(ArcSwap::from_pointee(config)); let handlers = handlers::setup(config.clone()); @@ -138,12 +123,7 @@ impl Application { })), handlers, ); - Self::load_configured_theme( - &mut editor, - &config.load(), - terminal.backend().supports_true_color(), - theme_mode, - ); + Self::load_configured_theme(&mut editor, &config.load()); let keys = Box::new(Map::new(Arc::clone(&config), |config: &Config| { &config.keys @@ -234,7 +214,7 @@ impl Application { } else { editor.new_file(Action::VerticalSplit); } - } else if stdin().is_terminal() || cfg!(feature = "integration") { + } else if stdin().is_tty() || cfg!(feature = "integration") { editor.new_file(Action::VerticalSplit); } else { editor @@ -262,7 +242,6 @@ impl Application { signals, jobs: Jobs::new(), lsp_progress: LspProgressMap::new(), - theme_mode, }; Ok(app) @@ -303,7 +282,7 @@ impl Application { pub async fn event_loop<S>(&mut self, input_stream: &mut S) where - S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin, + S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin, { self.render().await; @@ -316,7 +295,7 @@ impl Application { pub async fn event_loop_until_idle<S>(&mut self, input_stream: &mut S) -> bool where - S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin, + S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin, { loop { if self.editor.should_close() { @@ -377,8 +356,6 @@ impl Application { } pub fn handle_config_events(&mut self, config_event: ConfigEvent) { - let old_editor_config = self.editor.config(); - match config_event { ConfigEvent::Refresh => self.refresh_config(), @@ -388,7 +365,7 @@ impl Application { ConfigEvent::Update(editor_config) => { let mut app_config = (*self.config.load().clone()).clone(); app_config.editor = *editor_config; - if let Err(err) = self.terminal.reconfigure((&app_config.editor).into()) { + if let Err(err) = self.terminal.reconfigure(app_config.editor.clone().into()) { self.editor.set_error(err.to_string()); }; self.config.store(Arc::new(app_config)); @@ -397,7 +374,7 @@ impl Application { // Update all the relevant members in the editor after updating // the configuration. - self.editor.refresh_config(&old_editor_config); + self.editor.refresh_config(); // reset view position in case softwrap was enabled/disabled let scrolloff = self.editor.config().scrolloff; @@ -417,18 +394,11 @@ impl Application { // the sake of locals highlighting. let lang_loader = helix_core::config::user_lang_loader()?; self.editor.syn_loader.store(Arc::new(lang_loader)); - Self::load_configured_theme( - &mut self.editor, - &default_config, - self.terminal.backend().supports_true_color(), - self.theme_mode, - ); + Self::load_configured_theme(&mut self.editor, &default_config); // Re-parse any open documents with the new language config. let lang_loader = self.editor.syn_loader.load(); for document in self.editor.documents.values_mut() { - // Re-detect .editorconfig - document.detect_editor_config(); document.detect_language(&lang_loader); let diagnostics = Editor::doc_diagnostics( &self.editor.language_servers, @@ -438,7 +408,8 @@ impl Application { document.replace_diagnostics(diagnostics, &[], None); } - self.terminal.reconfigure((&default_config.editor).into())?; + self.terminal + .reconfigure(default_config.editor.clone().into())?; // Store new config self.config.store(Arc::new(default_config)); Ok(()) @@ -455,18 +426,12 @@ impl Application { } /// Load the theme set in configuration - fn load_configured_theme( - editor: &mut Editor, - config: &Config, - terminal_true_color: bool, - mode: Option<theme::Mode>, - ) { - let true_color = terminal_true_color || config.editor.true_color || crate::true_color(); + fn load_configured_theme(editor: &mut Editor, config: &Config) { + let true_color = config.editor.true_color || crate::true_color(); let theme = config .theme .as_ref() - .and_then(|theme_config| { - let theme = theme_config.choose(mode); + .and_then(|theme| { editor .theme_loader .load(theme) @@ -534,7 +499,7 @@ impl Application { // https://github.com/neovim/neovim/issues/12322 // https://github.com/neovim/neovim/pull/13084 for retries in 1..=10 { - match self.terminal.claim() { + match self.claim_term().await { Ok(()) => break, Err(err) if retries == 10 => panic!("Failed to claim terminal: {}", err), Err(_) => continue, @@ -542,7 +507,7 @@ impl Application { } // redraw the terminal - let area = self.terminal.size(); + let area = self.terminal.size().expect("couldn't get terminal size"); self.compositor.resize(area); self.terminal.clear().expect("couldn't clear terminal"); @@ -604,41 +569,24 @@ impl Application { doc.set_last_saved_revision(doc_save_event.revision, doc_save_event.save_time); let lines = doc_save_event.text.len_lines(); - let size = doc_save_event.text.len_bytes(); - - enum Size { - Bytes(u16), - HumanReadable(f32, &'static str), - } + let mut sz = doc_save_event.text.len_bytes() as f32; - impl std::fmt::Display for Size { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Bytes(bytes) => write!(f, "{bytes}B"), - Self::HumanReadable(size, suffix) => write!(f, "{size:.1}{suffix}"), - } - } + const SUFFIX: [&str; 4] = ["B", "KiB", "MiB", "GiB"]; + let mut i = 0; + while i < SUFFIX.len() - 1 && sz >= 1024.0 { + sz /= 1024.0; + i += 1; } - let size = if size < 1024 { - Size::Bytes(size as u16) - } else { - const SUFFIX: [&str; 4] = ["B", "KiB", "MiB", "GiB"]; - let mut size = size as f32; - let mut i = 0; - while i < SUFFIX.len() - 1 && size >= 1024.0 { - size /= 1024.0; - i += 1; - } - Size::HumanReadable(size, SUFFIX[i]) - }; - self.editor .set_doc_path(doc_save_event.doc_id, &doc_save_event.path); // TODO: fix being overwritten by lsp self.editor.set_status(format!( - "'{}' written, {lines}L {size}", + "'{}' written, {}L {:.1}{}", get_relative_path(&doc_save_event.path).to_string_lossy(), + lines, + sz, + SUFFIX[i], )); } @@ -660,8 +608,8 @@ impl Application { // limit render calls for fast language server messages helix_event::request_redraw(); } - EditorEvent::DebuggerEvent((id, payload)) => { - let needs_render = self.editor.handle_debugger_message(id, payload).await; + EditorEvent::DebuggerEvent(payload) => { + let needs_render = self.editor.handle_debugger_message(payload).await; if needs_render { self.render().await; } @@ -683,10 +631,7 @@ impl Application { false } - pub async fn handle_terminal_events(&mut self, event: std::io::Result<TerminalEvent>) { - #[cfg(not(windows))] - use termina::escape::csi; - + pub async fn handle_terminal_events(&mut self, event: std::io::Result<CrosstermEvent>) { let mut cx = crate::compositor::Context { editor: &mut self.editor, jobs: &mut self.jobs, @@ -694,51 +639,20 @@ impl Application { }; // Handle key events let should_redraw = match event.unwrap() { - #[cfg(not(windows))] - termina::Event::WindowResized(termina::WindowSize { rows, cols, .. }) => { - self.terminal - .resize(Rect::new(0, 0, cols, rows)) - .expect("Unable to resize terminal"); - - let area = self.terminal.size(); - - self.compositor.resize(area); - - self.compositor - .handle_event(&Event::Resize(cols, rows), &mut cx) - } - #[cfg(not(windows))] - // Ignore keyboard release events. - termina::Event::Key(termina::event::KeyEvent { - kind: termina::event::KeyEventKind::Release, - .. - }) => false, - #[cfg(not(windows))] - termina::Event::Csi(csi::Csi::Mode(csi::Mode::ReportTheme(mode))) => { - Self::load_configured_theme( - &mut self.editor, - &self.config.load(), - self.terminal.backend().supports_true_color(), - Some(mode.into()), - ); - true - } - #[cfg(windows)] - TerminalEvent::Resize(width, height) => { + CrosstermEvent::Resize(width, height) => { self.terminal .resize(Rect::new(0, 0, width, height)) .expect("Unable to resize terminal"); - let area = self.terminal.size(); + let area = self.terminal.size().expect("couldn't get terminal size"); self.compositor.resize(area); self.compositor .handle_event(&Event::Resize(width, height), &mut cx) } - #[cfg(windows)] // Ignore keyboard release events. - crossterm::event::Event::Key(crossterm::event::KeyEvent { + CrosstermEvent::Key(crossterm::event::KeyEvent { kind: crossterm::event::KeyEventKind::Release, .. }) => false, @@ -815,16 +729,23 @@ impl Application { log::error!("Discarding publishDiagnostic notification sent by an uninitialized server: {}", language_server.name()); return; } - let provider = helix_core::diagnostic::DiagnosticProvider::Lsp { + let provider = helix_view::diagnostic::DiagnosticProvider::Lsp { server_id, identifier: None, }; - self.editor.handle_lsp_diagnostics( - &provider, - uri, - params.version, - params.diagnostics, - ); + let diagnostics = params + .diagnostics + .into_iter() + .map(|diagnostic| { + helix_view::Diagnostic::lsp( + provider.clone(), + language_server.offset_encoding(), + diagnostic, + ) + }) + .collect(); + self.editor + .handle_diagnostics(&provider, uri, params.version, diagnostics); } Notification::ShowMessage(params) => { if self.config.load().editor.lsp.display_messages { @@ -930,8 +851,8 @@ impl Application { // we need to clear those and remove the entries from the list if this leads to // an empty diagnostic list for said files for diags in self.editor.diagnostics.values_mut() { - diags.retain(|(_, provider)| { - provider.language_server_id() != Some(server_id) + diags.retain(|diag| { + diag.provider.language_server_id() != Some(server_id) }); } @@ -1103,26 +1024,6 @@ impl Application { let result = self.handle_show_document(params, offset_encoding); Ok(json!(result)) } - Ok(MethodCall::WorkspaceDiagnosticRefresh) => { - let language_server = language_server!().id(); - - let documents: Vec<_> = self - .editor - .documents - .values() - .filter(|x| x.supports_language_server(language_server)) - .map(|x| x.id()) - .collect(); - - for document in documents { - handlers::diagnostics::request_document_diagnostics( - &mut self.editor, - document, - ); - } - - Ok(serde_json::Value::Null) - } }; let language_server = language_server!(); @@ -1201,60 +1102,36 @@ impl Application { lsp::ShowDocumentResult { success: true } } + async fn claim_term(&mut self) -> std::io::Result<()> { + let terminal_config = self.config.load().editor.clone().into(); + self.terminal.claim(terminal_config) + } + fn restore_term(&mut self) -> std::io::Result<()> { + let terminal_config = self.config.load().editor.clone().into(); use helix_view::graphics::CursorKind; self.terminal .backend_mut() .show_cursor(CursorKind::Block) .ok(); - self.terminal.restore() - } - - #[cfg(all(not(feature = "integration"), not(windows)))] - pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin { - use termina::{escape::csi, Terminal as _}; - let reader = self.terminal.backend().terminal().event_reader(); - termina::EventStream::new(reader, |event| { - // Accept either non-escape sequences or theme mode updates. - !event.is_escape() - || matches!( - event, - termina::Event::Csi(csi::Csi::Mode(csi::Mode::ReportTheme(_))) - ) - }) - } - - #[cfg(all(not(feature = "integration"), windows))] - pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin { - crossterm::event::EventStream::new() - } - - #[cfg(feature = "integration")] - pub fn event_stream(&self) -> impl Stream<Item = std::io::Result<TerminalEvent>> + Unpin { - use std::{ - pin::Pin, - task::{Context, Poll}, - }; - - /// A dummy stream that never polls as ready. - pub struct DummyEventStream; - - impl Stream for DummyEventStream { - type Item = std::io::Result<TerminalEvent>; - - fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { - Poll::Pending - } - } - - DummyEventStream + self.terminal.restore(terminal_config) } pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error> where - S: Stream<Item = std::io::Result<TerminalEvent>> + Unpin, + S: Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin, { - self.terminal.claim()?; + self.claim_term().await?; + + // Exit the alternate screen and disable raw mode before panicking + let hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + // We can't handle errors properly inside this closure. And it's + // probably not a good idea to `unwrap()` inside a panic handler. + // So we just ignore the `Result`. + let _ = TerminalBackend::force_restore(); + hook(info); + })); self.event_loop(input_stream).await; |