• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lpenz / ogle / 21724486044

05 Feb 2026 06:57PM UTC coverage: 58.407% (-0.2%) from 58.629%
21724486044

push

github

lpenz
Cargo.*: increment version to 2.3.3

462 of 791 relevant lines covered (58.41%)

1.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

29.03
/src/user_wrapper.rs
1
// Copyright (C) 2025 Leandro Lisboa Penz <lpenz@lpenz.org>
2
// This file is subject to the terms and conditions defined in
3
// file 'LICENSE', which is part of this source code package.
4

5
//! Wrapper for user interaction.
6
//!
7
//! For now we just check if the user has typed ENTER, which makes
8
//! ogle exit after the current run is over.
9

10
use color_eyre::Report;
11
use color_eyre::eyre::eyre;
12
use crossterm::tty::IsTty;
13
use crossterm::{
14
    event::{Event, EventStream, KeyCode, KeyEvent, KeyModifiers},
15
    terminal::{disable_raw_mode, enable_raw_mode},
16
};
17
use std::pin::Pin;
18
use std::task::{Context, Poll};
19
use tokio::io;
20
use tokio_stream::Stream;
21
use tracing::info;
22
use tracing::instrument;
23

24
/// An event coming from the user
25
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26
pub enum UserEvent {
27
    /// Let the current execution finish and exit instead of sleeping.
28
    Quit,
29
    /// Kill the underlying program immediately and exit.
30
    Kill,
31
}
32

33
impl TryFrom<KeyEvent> for UserEvent {
34
    type Error = Report;
35
    fn try_from(ke: KeyEvent) -> Result<Self, Self::Error> {
×
36
        if ke.code == KeyCode::Char('q')
×
37
            || (ke.code == KeyCode::Char('d') && ke.modifiers == KeyModifiers::CONTROL)
×
38
        {
39
            Ok(UserEvent::Quit)
×
40
        } else if ke.code == KeyCode::Char('k')
×
41
            || (ke.code == KeyCode::Char('c') && ke.modifiers == KeyModifiers::CONTROL)
×
42
        {
43
            Ok(UserEvent::Kill)
×
44
        } else {
45
            Err(eyre!("unrecognized key event {:?}", ke))
×
46
        }
47
    }
×
48
}
49

50
impl TryFrom<&Event> for UserEvent {
51
    type Error = Report;
52
    fn try_from(event: &Event) -> Result<Self, Self::Error> {
×
53
        match event {
×
54
            Event::Key(ke) => UserEvent::try_from(*ke),
×
55
            event => Err(eyre!("unrecognized event {:?}", event)),
×
56
        }
57
    }
×
58
}
59

60
/// A wrapper for `crossterm` that polls the keyboard and provides the
61
/// keypress in a tokio stream.
62
///
63
/// Also provides a virtual implementation for use in tests.
64
#[derive(Debug, Default)]
65
pub enum UserStream {
66
    /// A real implementation that gets KeyEvents from an EventStream
67
    Real(EventStream),
68
    /// A virtual implementation that doesn't do anything.
69
    #[default]
70
    Virtual,
71
}
72

73
impl UserStream {
74
    pub fn new_real() -> Option<UserStream> {
×
75
        let stdin = io::stdin();
×
76
        if stdin.is_tty() {
×
77
            let _ = enable_raw_mode();
×
78
            Some(UserStream::Real(EventStream::new()))
×
79
        } else {
80
            None
×
81
        }
82
    }
×
83

84
    pub fn new_virtual() -> UserStream {
2✔
85
        UserStream::Virtual
2✔
86
    }
2✔
87
}
88

89
impl Drop for UserStream {
90
    fn drop(&mut self) {
2✔
91
        if matches!(self, UserStream::Real(_)) {
2✔
92
            let _ = disable_raw_mode();
×
93
        }
2✔
94
    }
2✔
95
}
96

97
impl Stream for UserStream {
98
    type Item = UserEvent;
99

100
    #[instrument(level = "debug", ret, skip(cx))]
101
    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
2✔
102
        let this = self.get_mut();
103
        match this {
104
            UserStream::Real(event_stream) => {
105
                let next = Pin::new(event_stream).poll_next(cx);
106
                match next {
107
                    Poll::Ready(Some(Ok(event))) => match UserEvent::try_from(&event) {
108
                        Ok(user_event) => Poll::Ready(Some(user_event)),
109
                        Err(e) => {
110
                            info!(
111
                                event = ?event,
112
                                error=%e,
113
                                "error converting Event into UserEvent"
114
                            );
115
                            Poll::Pending
116
                        }
117
                    },
118
                    Poll::Ready(Some(Err(error))) => {
119
                        info!(
120
                            error = %error,
121
                            "EventStream yielded an error"
122
                        );
123
                        Poll::Pending
124
                    }
125
                    Poll::Ready(None) => Poll::Ready(None),
126
                    Poll::Pending => Poll::Pending,
127
                }
128
            }
129
            UserStream::Virtual => Poll::Ready(None),
130
        }
131
    }
2✔
132
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc