From 48b851cd8af33315d079e88eccffbbd7e6ed38b8 Mon Sep 17 00:00:00 2001 From: Lucas Pickering Date: Sun, 1 Sep 2024 09:07:48 -0400 Subject: [PATCH] Fix garbage events from vim Vim dumps out some garbage events on exit, which causes strange behavior in Slumber. Closes #351 --- CHANGELOG.md | 1 + crates/slumber_tui/src/lib.rs | 23 +++++++++++++++++------ crates/slumber_tui/src/util.rs | 9 +++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3bd7782..e8295af3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Here's the full list of changes: - E.g. F5 will now refresh the collection while a text box is in focus - Redraw TUI when terminal is resized - Clamp text window scroll state when window is resized or text changes +- Fix extraneous input events when exiting Vim [#351](https://github.com/LucasPickering/slumber/issues/351) ## [1.8.1] - 2024-08-11 diff --git a/crates/slumber_tui/src/lib.rs b/crates/slumber_tui/src/lib.rs index aa7ef406..24ede414 100644 --- a/crates/slumber_tui/src/lib.rs +++ b/crates/slumber_tui/src/lib.rs @@ -18,7 +18,10 @@ mod view; use crate::{ context::TuiContext, message::{Message, MessageSender, RequestConfig}, - util::{get_editor_command, save_file, signals, ResultReported}, + util::{ + clear_event_buffer, get_editor_command, save_file, signals, + ResultReported, + }, view::{PreviewPrompter, RequestState, View}, }; use anyhow::{anyhow, Context}; @@ -29,7 +32,7 @@ use crossterm::{ }, terminal::{EnterAlternateScreen, LeaveAlternateScreen}, }; -use futures::{Future, StreamExt}; +use futures::StreamExt; use notify::{event::ModifyKind, RecursiveMode, Watcher}; use ratatui::{prelude::CrosstermBackend, Terminal}; use slumber_config::{Action, Config}; @@ -40,6 +43,7 @@ use slumber_core::{ template::{Prompter, Template, TemplateChunk, TemplateContext}, }; use std::{ + future::Future, io::{self, Stdout}, ops::Deref, path::{Path, PathBuf}, @@ -418,11 +422,18 @@ impl Tui { self.terminal.draw(|frame| { frame.render_widget("Waiting for editor to close...", frame.area()); })?; + + let mut stdout = io::stdout(); + crossterm::execute!(stdout, LeaveAlternateScreen)?; command.status().context(error_context)?; - // If the editor was terminal-based it probably dropped us out of the - // alternate screen when it closed, so get back in there. This is - // idempotent so no harm in doing it - crossterm::execute!(io::stdout(), EnterAlternateScreen)?; + // Some editors *cough* vim *cough* dump garbage to the event buffer on + // exit. I've never figured out what actually causes it, but a simple + // solution is to dump all events in the buffer before returning to + // Slumber. It's possible we lose some real user input here (e.g. if + // other events were queued behind the event to open the editor). + clear_event_buffer(); + crossterm::execute!(stdout, EnterAlternateScreen)?; + // Redraw immediately. The main loop will probably be in the tick // timeout when we go back to it, so that adds a 250ms delay to // redrawing the screen that we want to skip. diff --git a/crates/slumber_tui/src/util.rs b/crates/slumber_tui/src/util.rs index ddcd3935..454c85b5 100644 --- a/crates/slumber_tui/src/util.rs +++ b/crates/slumber_tui/src/util.rs @@ -4,6 +4,7 @@ use crate::{ view::Confirm, }; use anyhow::Context; +use crossterm::event; use editor_command::EditorBuilder; use futures::{future, FutureExt}; use slumber_core::{ @@ -15,6 +16,7 @@ use std::{ ops::Deref, path::{Path, PathBuf}, process::Command, + time::Duration, }; use tokio::{fs::OpenOptions, io::AsyncWriteExt, sync::oneshot}; use tracing::{debug, error, info, warn}; @@ -45,6 +47,13 @@ where } } +/// Clear all input events in the terminal event buffer +pub fn clear_event_buffer() { + while let Ok(true) = event::poll(Duration::from_millis(0)) { + let _ = event::read(); + } +} + /// Listen for any exit signals, and return `Ok(())` when any signal is /// received. This can only fail during initialization. #[cfg(unix)]