@@ -14,6 +14,41 @@ use super::types::Theme;
1414/// Increased to 250ms to allow slower terminals to respond.
1515const OSC_QUERY_TIMEOUT_MS : u64 = 250 ;
1616
17+ /// Number of drain iterations to perform after OSC query.
18+ /// Multiple passes with small delays help catch delayed response bytes.
19+ const DRAIN_ITERATIONS : usize = 3 ;
20+
21+ /// Delay between drain iterations (milliseconds).
22+ /// Short enough to not noticeably slow down reload, but enough to catch stragglers.
23+ const DRAIN_DELAY_MS : u64 = 10 ;
24+
25+ /// Drain any stray events from crossterm's event queue.
26+ ///
27+ /// OSC responses may arrive in chunks or with slight delays. Some terminals
28+ /// also echo the response to stdin in addition to /dev/tty. This function
29+ /// drains events multiple times with small delays to ensure all stray bytes
30+ /// are consumed before returning control to the main event loop.
31+ ///
32+ /// Without this, OSC response bytes like `10;rgb:e0e0/dede/f4f4` can leak
33+ /// into the search input field when reloading theme with Ctrl+R.
34+ fn drain_stray_events ( ) {
35+ use crossterm:: event:: { poll, read as crossterm_read} ;
36+
37+ for _ in 0 ..DRAIN_ITERATIONS {
38+ // Drain all immediately available events
39+ while poll ( Duration :: from_millis ( 0 ) ) . unwrap_or ( false ) {
40+ let _ = crossterm_read ( ) ;
41+ }
42+ // Small delay to allow any in-flight bytes to arrive
43+ std:: thread:: sleep ( Duration :: from_millis ( DRAIN_DELAY_MS ) ) ;
44+ }
45+
46+ // Final drain after the last delay
47+ while poll ( Duration :: from_millis ( 0 ) ) . unwrap_or ( false ) {
48+ let _ = crossterm_read ( ) ;
49+ }
50+ }
51+
1752/// What: Query the terminal for foreground and background colors.
1853///
1954/// Inputs:
@@ -53,7 +88,7 @@ pub fn query_terminal_colors() -> Option<(Color, Color)> {
5388
5489/// Perform the actual query with terminal in raw mode.
5590fn query_with_raw_mode ( ) -> Option < ( Color , Color ) > {
56- use crossterm:: event:: { DisableMouseCapture , EnableMouseCapture , poll , read as crossterm_read } ;
91+ use crossterm:: event:: { DisableMouseCapture , EnableMouseCapture } ;
5792 use crossterm:: execute;
5893 use crossterm:: terminal:: { disable_raw_mode, enable_raw_mode, is_raw_mode_enabled} ;
5994
@@ -74,10 +109,9 @@ fn query_with_raw_mode() -> Option<(Color, Color)> {
74109 return None ;
75110 }
76111
77- // Drain any pending events before sending query
78- while poll ( Duration :: from_millis ( 0 ) ) . unwrap_or ( false ) {
79- let _ = crossterm_read ( ) ;
80- }
112+ // Drain any pending events before sending query.
113+ // This clears any events that accumulated before we started.
114+ drain_stray_events ( ) ;
81115
82116 let result = ( || {
83117 let mut stdout = std:: io:: stdout ( ) ;
@@ -114,10 +148,11 @@ fn query_with_raw_mode() -> Option<(Color, Color)> {
114148 Some ( ( fg?, bg?) )
115149 } ) ( ) ;
116150
117- // Drain any events that arrived during the query
118- while poll ( Duration :: from_millis ( 0 ) ) . unwrap_or ( false ) {
119- let _ = crossterm_read ( ) ;
120- }
151+ // Drain any events that arrived during the query.
152+ // The OSC response may arrive in chunks or be delayed, so we drain multiple times
153+ // with small delays to ensure all stray bytes are consumed before returning control
154+ // to the main event loop. This prevents OSC responses from leaking into input fields.
155+ drain_stray_events ( ) ;
121156
122157 // Restore terminal mode if we enabled it
123158 if !was_raw_mode {
0 commit comments