scm_record/
helpers.rs

1//! Helper functions for rendering UI components.
2
3use std::{collections::VecDeque, time::Duration};
4
5use crate::{Event, RecordError, RecordInput, TerminalKind};
6
7/// Generate a one-line description of a binary file change.
8pub fn make_binary_description(hash: &str, num_bytes: u64) -> String {
9    format!("{hash} ({num_bytes} bytes)")
10}
11
12/// Reads input events from the terminal using `crossterm`.
13///
14/// Its default implementation of `edit_commit_message` returns the provided
15/// message unchanged.
16pub struct CrosstermInput;
17
18impl RecordInput for CrosstermInput {
19    fn terminal_kind(&self) -> TerminalKind {
20        TerminalKind::Crossterm
21    }
22
23    fn next_events(&mut self) -> Result<Vec<Event>, RecordError> {
24        // Ensure we block for at least one event.
25        let first_event =
26            crossterm::event::read().map_err(|err| RecordError::ReadInput(err.into()))?;
27        let mut events = vec![first_event.into()];
28        // Some events, like scrolling, are generated more quickly than
29        // we can render the UI. In those cases, batch up all available
30        // events and process them before the next render.
31        while crossterm::event::poll(Duration::ZERO)
32            .map_err(|err| RecordError::ReadInput(err.into()))?
33        {
34            let event =
35                crossterm::event::read().map_err(|err| RecordError::ReadInput(err.into()))?;
36            events.push(event.into());
37        }
38        Ok(events)
39    }
40
41    fn edit_commit_message(&mut self, message: &str) -> Result<String, RecordError> {
42        Ok(message.to_owned())
43    }
44}
45
46/// Reads events from the provided sequence of events.
47pub struct TestingInput {
48    /// The width of the virtual terminal in columns.
49    pub width: usize,
50
51    /// The height of the virtual terminal in columns.
52    pub height: usize,
53
54    /// The sequence of events to emit.
55    pub events: Box<dyn Iterator<Item = Event>>,
56
57    /// Commit messages to use when the commit editor is opened.
58    pub commit_messages: VecDeque<String>,
59}
60
61impl TestingInput {
62    /// Helper function to construct a `TestingInput`.
63    pub fn new(
64        width: usize,
65        height: usize,
66        events: impl IntoIterator<Item = Event> + 'static,
67    ) -> Self {
68        Self {
69            width,
70            height,
71            events: Box::new(events.into_iter()),
72            commit_messages: Default::default(),
73        }
74    }
75}
76
77impl RecordInput for TestingInput {
78    fn terminal_kind(&self) -> TerminalKind {
79        let Self {
80            width,
81            height,
82            events: _,
83            commit_messages: _,
84        } = self;
85        TerminalKind::Testing {
86            width: *width,
87            height: *height,
88        }
89    }
90
91    fn next_events(&mut self) -> Result<Vec<Event>, RecordError> {
92        Ok(vec![self.events.next().unwrap_or(Event::None)])
93    }
94
95    fn edit_commit_message(&mut self, _message: &str) -> Result<String, RecordError> {
96        self.commit_messages
97            .pop_front()
98            .ok_or_else(|| RecordError::Other("No more commit messages available".to_string()))
99    }
100}