#[derive(Debug, Clone)]
pub struct Hint {
pub text: String,
pub before: String,
pub after: String,
}
impl Hint {
#[must_use]
pub fn new(text: impl Into<String>) -> Self {
Hint {
text: text.into(),
before: "\x1b[90m".to_string(),
after: "\x1b[39m".to_string(),
}
}
#[must_use]
pub fn styled(mut self, before: impl Into<String>, after: impl Into<String>) -> Self {
self.before = before.into();
self.after = after.into();
self
}
#[must_use]
pub fn render(&self) -> String {
format!("{}{}{}", self.before, self.text, self.after)
}
}
pub trait HintProvider {
fn hint(&self, line: &str, pos: usize) -> Option<Hint>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoHinter;
impl HintProvider for NoHinter {
fn hint(&self, _line: &str, _pos: usize) -> Option<Hint> {
None
}
}
impl<F> HintProvider for F
where
F: Fn(&str, usize) -> Option<Hint>,
{
fn hint(&self, line: &str, pos: usize) -> Option<Hint> {
self(line, pos)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_styling_wraps_the_text() {
let hint = Hint::new(" (try --help)");
assert_eq!(hint.render(), "\x1b[90m (try --help)\x1b[39m");
}
#[test]
fn custom_styling_is_used() {
let hint = Hint::new("x").styled("\x1b[31m", "\x1b[0m");
assert_eq!(hint.render(), "\x1b[31mx\x1b[0m");
}
#[test]
fn closures_implement_the_trait() {
let provider = |line: &str, _pos: usize| {
if line == "git" {
Some(Hint::new(" status"))
} else {
None
}
};
assert!(provider.hint("git", 3).is_some());
assert!(provider.hint("cargo", 5).is_none());
assert!(NoHinter.hint("anything", 0).is_none());
}
}