зеркало из
https://github.com/glebtv/tui-textarea.git
synced 2026-08-29 03:56:18 +03:00
Merge pull request #32 from pm100/password
Allow masking a text with a specific character
Этот коммит содержится в:
@@ -63,6 +63,10 @@ required-features = ["crossterm"]
|
||||
name = "single_line"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "password"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
[[example]]
|
||||
name = "variable"
|
||||
required-features = ["crossterm"]
|
||||
|
||||
62
examples/password.rs
Обычный файл
62
examples/password.rs
Обычный файл
@@ -0,0 +1,62 @@
|
||||
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||
use crossterm::terminal::{
|
||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||
};
|
||||
use std::io;
|
||||
use tui::backend::CrosstermBackend;
|
||||
use tui::layout::{Constraint, Layout};
|
||||
use tui::style::{Color, Style};
|
||||
use tui::widgets::{Block, Borders};
|
||||
use tui::Terminal;
|
||||
use tui_textarea::{Input, Key, TextArea};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
enable_raw_mode()?;
|
||||
crossterm::execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut term = Terminal::new(backend)?;
|
||||
|
||||
let mut textarea = TextArea::default();
|
||||
textarea.set_cursor_line_style(Style::default());
|
||||
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));
|
||||
textarea.set_block(Block::default().borders(Borders::ALL).title("Password"));
|
||||
loop {
|
||||
term.draw(|f| {
|
||||
let chunks = layout.split(f.size());
|
||||
let widget = textarea.widget();
|
||||
f.render_widget(widget, chunks[0]);
|
||||
})?;
|
||||
|
||||
match crossterm::event::read()?.into() {
|
||||
Input { key: Key::Esc, .. } => break,
|
||||
Input {
|
||||
key: Key::Enter, ..
|
||||
} => break,
|
||||
|
||||
input => {
|
||||
// TextArea::input returns if the input modified its text
|
||||
if textarea.input(input) {
|
||||
// is_valid = validate(&mut textarea);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
disable_raw_mode()?;
|
||||
crossterm::execute!(
|
||||
term.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
term.show_cursor()?;
|
||||
|
||||
println!("Input: {:?}", textarea.lines()[0]);
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use crate::tui::style::Style;
|
||||
feature = "ratatui-your-backend",
|
||||
))]
|
||||
use crate::tui::text::Line as Spans;
|
||||
|
||||
use crate::tui::text::Span;
|
||||
#[cfg(not(any(
|
||||
feature = "ratatui-crossterm",
|
||||
@@ -12,6 +13,7 @@ use crate::tui::text::Span;
|
||||
feature = "ratatui-your-backend",
|
||||
)))]
|
||||
use crate::tui::text::Spans;
|
||||
|
||||
use crate::util::{num_digits, spaces};
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
@@ -46,27 +48,39 @@ impl Boundary {
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_tabs(s: &str, tab_len: u8) -> 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<char>) -> 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());
|
||||
}
|
||||
}
|
||||
if buf.is_empty() {
|
||||
Cow::Borrowed(s)
|
||||
} else {
|
||||
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> {
|
||||
@@ -119,7 +133,7 @@ impl<'a> LineHighlighter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_spans(self) -> Spans<'a> {
|
||||
pub fn into_spans(self, mask: Option<char>) -> Spans<'a> {
|
||||
let Self {
|
||||
line,
|
||||
mut spans,
|
||||
@@ -131,7 +145,7 @@ impl<'a> LineHighlighter<'a> {
|
||||
} = self;
|
||||
|
||||
if boundaries.is_empty() {
|
||||
spans.push(Span::styled(replace_tabs(line, tab_len), style_begin));
|
||||
spans.push(Span::styled(prepare_line(line, tab_len, mask), style_begin));
|
||||
if cursor_at_end {
|
||||
spans.push(Span::styled(" ", cursor_style));
|
||||
}
|
||||
@@ -152,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),
|
||||
prepare_line(&line[start..end], tab_len, mask),
|
||||
style,
|
||||
));
|
||||
}
|
||||
@@ -166,7 +180,10 @@ impl<'a> LineHighlighter<'a> {
|
||||
start = end;
|
||||
} else {
|
||||
if start != line.len() {
|
||||
spans.push(Span::styled(replace_tabs(&line[start..], tab_len), style));
|
||||
spans.push(Span::styled(
|
||||
prepare_line(&line[start..], tab_len, mask),
|
||||
style,
|
||||
));
|
||||
}
|
||||
if cursor_at_end {
|
||||
spans.push(Span::styled(" ", cursor_style));
|
||||
|
||||
@@ -63,6 +63,7 @@ pub struct TextArea<'a> {
|
||||
alignment: Alignment,
|
||||
pub(crate) placeholder: String,
|
||||
pub(crate) placeholder_style: Style,
|
||||
mask: Option<char>,
|
||||
}
|
||||
|
||||
/// Convert any iterator whose elements can be converted into [`String`] into [`TextArea`]. Each [`String`] element is
|
||||
@@ -162,6 +163,7 @@ impl<'a> TextArea<'a> {
|
||||
alignment: Alignment::Left,
|
||||
placeholder: String::new(),
|
||||
placeholder_style: Style::default().fg(Color::DarkGray),
|
||||
mask: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1005,7 +1007,7 @@ impl<'a> TextArea<'a> {
|
||||
hl.search(matches, self.search.style);
|
||||
}
|
||||
|
||||
hl.into_spans()
|
||||
hl.into_spans(self.mask)
|
||||
}
|
||||
|
||||
/// Build a tui-rs widget to render the current state of the textarea. The widget instance returned from this
|
||||
@@ -1304,6 +1306,18 @@ impl<'a> TextArea<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies that the text should be masked with the specified character
|
||||
///
|
||||
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.
|
||||
/// ```
|
||||
|
||||
Ссылка в новой задаче
Block a user