diff --git a/examples/password.rs b/examples/password.rs index 8a983dc..79b4d28 100644 --- a/examples/password.rs +++ b/examples/password.rs @@ -21,8 +21,8 @@ fn main() -> io::Result<()> { let mut textarea = TextArea::default(); textarea.set_cursor_line_style(Style::default()); - textarea.set_password_mode(true); - textarea.set_placeholder_text("please enter your password"); + textarea.set_mask_char('\u{2022}'); //U+2022 BULLET (•) + textarea.set_placeholder_text("Please enter your password"); let layout = Layout::default().constraints([Constraint::Length(3), Constraint::Min(1)].as_slice()); textarea.set_style(Style::default().fg(Color::LightGreen)); diff --git a/src/highlight.rs b/src/highlight.rs index da118b7..e3e1cba 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -48,28 +48,39 @@ impl Boundary { } } -fn replace_tabs(s: &str, tab_len: u8, mask: bool) -> Cow<'_, str> { - let tab = spaces(tab_len); - let mut buf = String::new(); - for (i, c) in s.char_indices() { - if buf.is_empty() { - if c == '\t' { - buf.reserve(s.len()); - buf.push_str(&s[..i]); - buf.push_str(tab); - } - } else if c == '\t' { - buf.push_str(tab); - } else { - buf.push(c); +fn prepare_line(s: &str, tab_len: u8, mask: Option) -> Cow<'_, str> { + // two tasks performed here + // - mask out chars if mask is Some + // - replace hard tabs by the correct number of spaces + // (soft tabs were done earlier in 'insert_tab') + match mask { + Some(ch) => { + // no tab processing in the mask case + return Cow::Owned(s.chars().map(|_| ch).collect()); } - } - match (buf.is_empty(), mask) { - (true, true) => Cow::Owned(s.chars().map(|_| '*').collect()), - (true, false) => Cow::Borrowed(s), - (false, true) => Cow::Owned(buf.chars().map(|_| '*').collect()), - (false, false) => Cow::Owned(buf), - } + None => { + let tab = spaces(tab_len); + let mut buf = String::new(); + for (i, c) in s.char_indices() { + if buf.is_empty() { + if c == '\t' { + buf.reserve(s.len()); + buf.push_str(&s[..i]); + buf.push_str(tab); + } + } else if c == '\t' { + buf.push_str(tab); + } else { + buf.push(c); + } + } + if !buf.is_empty() { + return Cow::Owned(buf); + } + } + }; + // drop through in the case of no mask and no tabs + return Cow::Borrowed(s); } pub struct LineHighlighter<'a> { @@ -122,7 +133,7 @@ impl<'a> LineHighlighter<'a> { } } - pub fn into_spans(self, mask: bool) -> Spans<'a> { + pub fn into_spans(self, mask: Option) -> Spans<'a> { let Self { line, mut spans, @@ -134,7 +145,7 @@ impl<'a> LineHighlighter<'a> { } = self; if boundaries.is_empty() { - spans.push(Span::styled(replace_tabs(line, tab_len, mask), style_begin)); + spans.push(Span::styled(prepare_line(line, tab_len, mask), style_begin)); if cursor_at_end { spans.push(Span::styled(" ", cursor_style)); } @@ -155,7 +166,7 @@ impl<'a> LineHighlighter<'a> { if let Some((next_boundary, end)) = boundaries.next() { if start < end { spans.push(Span::styled( - replace_tabs(&line[start..end], tab_len, mask), + prepare_line(&line[start..end], tab_len, mask), style, )); } @@ -170,7 +181,7 @@ impl<'a> LineHighlighter<'a> { } else { if start != line.len() { spans.push(Span::styled( - replace_tabs(&line[start..], tab_len, mask), + prepare_line(&line[start..], tab_len, mask), style, )); } diff --git a/src/textarea.rs b/src/textarea.rs index ca199fb..96d8de9 100644 --- a/src/textarea.rs +++ b/src/textarea.rs @@ -63,7 +63,7 @@ pub struct TextArea<'a> { alignment: Alignment, pub(crate) placeholder: String, pub(crate) placeholder_style: Style, - password_mode: bool, + mask: Option, } /// Convert any iterator whose elements can be converted into [`String`] into [`TextArea`]. Each [`String`] element is @@ -163,7 +163,7 @@ impl<'a> TextArea<'a> { alignment: Alignment::Left, placeholder: String::new(), placeholder_style: Style::default().fg(Color::DarkGray), - password_mode: false, + mask: None, } } @@ -1007,7 +1007,7 @@ impl<'a> TextArea<'a> { hl.search(matches, self.search.style); } - hl.into_spans(self.password_mode) + hl.into_spans(self.mask) } /// Build a tui-rs widget to render the current state of the textarea. The widget instance returned from this @@ -1306,11 +1306,18 @@ impl<'a> TextArea<'a> { } } - /// Sets a flag indicating if the text should be masked with * characters + /// Specifies that the text should be masked with the specified character /// - pub fn set_password_mode(&mut self, value: bool) { - self.password_mode = value; + pub fn set_mask_char(&mut self, mask: char) { + self.mask = Some(mask); } + + /// Clear the previously set masking character + /// + pub fn clear_mask_char(&mut self) { + self.mask = None; + } + /// Set the style of cursor. By default, a cursor is rendered in the reversed color. Setting the same style as /// cursor line hides a cursor. /// ```