@@ -62,10 +62,10 @@ const POLL_WAIT: Duration = Duration::from_millis(100);
6262// before it is considered a paste. 10 events is conservative enough.
6363const EVENTS_THRESHOLD : usize = 10 ;
6464
65- /// Maximum time Reedline will block on input before yielding control to
66- /// external printers.
67- # [ cfg ( feature = "external_printer" ) ]
68- const EXTERNAL_PRINTER_WAIT : Duration = Duration :: from_millis ( 100 ) ;
65+ /// Default maximum time Reedline will block on input before yielding control
66+ /// for features that require periodic processing (e.g., external printer,
67+ /// idle callback).
68+ const DEFAULT_POLL_INTERVAL : Duration = Duration :: from_millis ( 100 ) ;
6969
7070/// Determines if inputs should be used to extend the regular line buffer,
7171/// traverse the history in the standard prompt or edit the search string in the
@@ -195,8 +195,18 @@ pub struct Reedline {
195195 // Whether lines should be accepted immediately
196196 immediately_accept : bool ,
197197
198+ // Maximum time to block on input before yielding control for features that
199+ // require periodic processing (external printer, idle callback).
200+ // Only used when external_printer or idle_callback is configured.
201+ poll_interval : Duration ,
202+
198203 #[ cfg( feature = "external_printer" ) ]
199204 external_printer : Option < ExternalPrinter < String > > ,
205+
206+ // Callback function that is called periodically while waiting for input.
207+ // Useful for processing external events (e.g., GUI updates) during idle time.
208+ #[ cfg( feature = "idle_callback" ) ]
209+ idle_callback : Option < Box < dyn FnMut ( ) + Send > > ,
200210}
201211
202212struct BufferEditor {
@@ -271,8 +281,11 @@ impl Reedline {
271281 bracketed_paste : BracketedPasteGuard :: default ( ) ,
272282 kitty_protocol : KittyProtocolGuard :: default ( ) ,
273283 immediately_accept : false ,
284+ poll_interval : DEFAULT_POLL_INTERVAL ,
274285 #[ cfg( feature = "external_printer" ) ]
275286 external_printer : None ,
287+ #[ cfg( feature = "idle_callback" ) ]
288+ idle_callback : None ,
276289 }
277290 }
278291
@@ -770,6 +783,12 @@ impl Reedline {
770783 self . repaint ( prompt) ?;
771784
772785 loop {
786+ // Call idle callback if set (for processing external events like GUI updates)
787+ #[ cfg( feature = "idle_callback" ) ]
788+ if let Some ( ref mut callback) = self . idle_callback {
789+ callback ( ) ;
790+ }
791+
773792 #[ cfg( feature = "external_printer" ) ]
774793 if let Some ( ref external_printer) = self . external_printer {
775794 // get messages from printer as crlf separated "lines"
@@ -805,15 +824,31 @@ impl Reedline {
805824 let mut events: Vec < Event > = vec ! [ ] ;
806825
807826 if !self . immediately_accept {
808- // If the `external_printer` feature is enabled, we need to
809- // periodically yield so that external printers get a chance to
810- // print. Otherwise, we can just block until we receive an event.
811- #[ cfg( feature = "external_printer" ) ]
812- if event:: poll ( EXTERNAL_PRINTER_WAIT ) ? {
827+ // Determine if we need to poll (non-blocking) or can block on input.
828+ // We need polling if external_printer or idle_callback is configured,
829+ // using the shared poll_interval for the timeout.
830+ let needs_polling = {
831+ #[ allow( unused_mut) ]
832+ let mut result = false ;
833+ #[ cfg( feature = "external_printer" ) ]
834+ if self . external_printer . is_some ( ) {
835+ result = true ;
836+ }
837+ #[ cfg( feature = "idle_callback" ) ]
838+ if self . idle_callback . is_some ( ) {
839+ result = true ;
840+ }
841+ result
842+ } ;
843+
844+ if needs_polling {
845+ if event:: poll ( self . poll_interval ) ? {
846+ events. push ( crossterm:: event:: read ( ) ?) ;
847+ }
848+ } else {
849+ // Block until we receive an event
813850 events. push ( crossterm:: event:: read ( ) ?) ;
814851 }
815- #[ cfg( not( feature = "external_printer" ) ) ]
816- events. push ( crossterm:: event:: read ( ) ?) ;
817852
818853 // Receive all events in the queue without blocking. Will stop when
819854 // a line of input is completed.
@@ -1976,6 +2011,59 @@ impl Reedline {
19762011 self
19772012 }
19782013
2014+ /// Sets the poll interval used when features that require periodic processing
2015+ /// are active (e.g., external printer, idle callback).
2016+ ///
2017+ /// This controls how frequently Reedline yields control back to these features
2018+ /// while waiting for user input. The default is 100ms.
2019+ ///
2020+ /// Common values are 33ms (~30fps) for UI updates or 100ms for less frequent tasks.
2021+ ///
2022+ /// Note: This setting only takes effect when an external printer or idle callback
2023+ /// is configured. Without these features, Reedline blocks until input is received.
2024+ ///
2025+ /// # Example
2026+ /// ```no_run
2027+ /// use std::time::Duration;
2028+ /// use reedline::Reedline;
2029+ ///
2030+ /// let editor = Reedline::create()
2031+ /// .with_poll_interval(Duration::from_millis(50));
2032+ /// ```
2033+ pub fn with_poll_interval ( mut self , interval : Duration ) -> Self {
2034+ self . poll_interval = interval;
2035+ self
2036+ }
2037+
2038+ /// Sets an idle callback that is called periodically while waiting for user input.
2039+ ///
2040+ /// This is useful for applications that need to process external events
2041+ /// (such as GUI updates, network events, or timer-based operations) while
2042+ /// the user is typing or the editor is waiting for input.
2043+ ///
2044+ /// Use [`with_poll_interval`](Self::with_poll_interval) to control how frequently
2045+ /// the callback is invoked (default: 100ms).
2046+ ///
2047+ /// ## Required feature:
2048+ /// `idle_callback`
2049+ ///
2050+ /// # Example
2051+ /// ```no_run
2052+ /// use std::time::Duration;
2053+ /// use reedline::Reedline;
2054+ ///
2055+ /// let editor = Reedline::create()
2056+ /// .with_poll_interval(Duration::from_millis(33))
2057+ /// .with_idle_callback(Box::new(|| {
2058+ /// // Process external events here
2059+ /// }));
2060+ /// ```
2061+ #[ cfg( feature = "idle_callback" ) ]
2062+ pub fn with_idle_callback ( mut self , callback : Box < dyn FnMut ( ) + Send > ) -> Self {
2063+ self . idle_callback = Some ( callback) ;
2064+ self
2065+ }
2066+
19792067 #[ cfg( feature = "external_printer" ) ]
19802068 fn external_messages ( external_printer : & ExternalPrinter < String > ) -> Result < Vec < String > > {
19812069 let mut messages = Vec :: new ( ) ;
@@ -2095,6 +2183,38 @@ mod tests {
20952183 f ( Reedline :: create ( ) ) ;
20962184 }
20972185
2186+ #[ test]
2187+ #[ cfg( feature = "idle_callback" ) ]
2188+ fn thread_safe_with_idle_callback ( ) {
2189+ use std:: sync:: atomic:: { AtomicUsize , Ordering } ;
2190+ use std:: sync:: Arc ;
2191+
2192+ fn f < S : Send > ( _: S ) { }
2193+
2194+ let counter = Arc :: new ( AtomicUsize :: new ( 0 ) ) ;
2195+ let counter_clone = counter. clone ( ) ;
2196+
2197+ let reedline = Reedline :: create ( )
2198+ . with_poll_interval ( Duration :: from_millis ( 100 ) )
2199+ . with_idle_callback ( Box :: new ( move || {
2200+ counter_clone. fetch_add ( 1 , Ordering :: SeqCst ) ;
2201+ } ) ) ;
2202+
2203+ // Verify that Reedline with idle_callback is still Send
2204+ f ( reedline) ;
2205+ }
2206+
2207+ #[ test]
2208+ #[ cfg( feature = "idle_callback" ) ]
2209+ fn idle_callback_builder_pattern ( ) {
2210+ // Test that with_idle_callback can be chained with other builder methods
2211+ let _reedline = Reedline :: create ( )
2212+ . with_quick_completions ( true )
2213+ . with_poll_interval ( Duration :: from_millis ( 33 ) )
2214+ . with_idle_callback ( Box :: new ( || { } ) )
2215+ . with_partial_completions ( true ) ;
2216+ }
2217+
20982218 #[ test]
20992219 fn mouse_click_moves_cursor_in_regular_mode ( ) {
21002220 let mut reedline = Reedline :: create ( ) . with_mouse_click ( MouseClickMode :: Enabled ) ;
0 commit comments