#[cfg(not(all(feature = "validation", feature = "gui", feature = "cursor-style")))]
compile_error!(
"This example requires the 'validation', 'gui' and 'cursor-style' features. \
Run with: cargo run --example validation_advanced_patterns --features \"validation,gui,cursor-style\""
);
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{
Frame, Terminal,
backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Style},
text::{Line, Span},
widgets::{Block, Borders, Paragraph, Wrap},
};
use std::io;
use std::sync::Arc;
use tui_canvas::ValidationResult;
use tui_canvas::{
AppMode, CharacterFilter, CursorManager, DataProvider, PatternFilters, PositionFilter,
PositionRange, TextFormState, ValidationConfig, ValidationConfigBuilder, render_canvas_default,
};
struct AdvancedPatternTextForm<D: DataProvider> {
editor: TextFormState<D>,
debug_message: String,
command_buffer: String,
validation_enabled: bool,
field_switch_blocked: bool,
block_reason: Option<String>,
}
impl<D: DataProvider> AdvancedPatternTextForm<D> {
fn new(data_provider: D) -> Self {
let mut editor = TextFormState::new(data_provider);
editor.set_validation_enabled(true);
Self {
editor,
debug_message:
"๐ Advanced Pattern Validation - Showcasing edge cases and complex patterns!"
.to_string(),
command_buffer: String::new(),
validation_enabled: true,
field_switch_blocked: false,
block_reason: None,
}
}
fn clear_command_buffer(&mut self) {
self.command_buffer.clear();
}
fn add_to_command_buffer(&mut self, ch: char) {
self.command_buffer.push(ch);
}
fn get_command_buffer(&self) -> &str {
&self.command_buffer
}
fn has_pending_command(&self) -> bool {
!self.command_buffer.is_empty()
}
fn toggle_validation(&mut self) {
self.validation_enabled = !self.validation_enabled;
self.editor.set_validation_enabled(self.validation_enabled);
if self.validation_enabled {
self.debug_message = "โ
Advanced Pattern Validation ENABLED".to_string();
} else {
self.debug_message = "โ Advanced Pattern Validation DISABLED".to_string();
}
}
fn move_left(&mut self) {
self.editor.move_left();
self.field_switch_blocked = false;
self.block_reason = None;
}
fn move_right(&mut self) {
self.editor.move_right();
self.field_switch_blocked = false;
self.block_reason = None;
}
fn move_up(&mut self) {
if self.editor.move_up() {
self.update_field_validation_status();
self.field_switch_blocked = false;
self.block_reason = None;
} else if let Some(reason) = self.editor.field_switch_block_reason() {
self.field_switch_blocked = true;
self.block_reason = Some(reason.clone());
self.debug_message = format!("๐ซ Field switch blocked: {reason}");
}
}
fn move_down(&mut self) {
if self.editor.move_down() {
self.update_field_validation_status();
self.field_switch_blocked = false;
self.block_reason = None;
} else if let Some(reason) = self.editor.field_switch_block_reason() {
self.field_switch_blocked = true;
self.block_reason = Some(reason.clone());
self.debug_message = format!("๐ซ Field switch blocked: {reason}");
}
}
fn move_line_start(&mut self) {
self.editor.move_line_start();
}
fn move_line_end(&mut self) {
self.editor.move_line_end();
}
fn enter_edit_mode(&mut self) {
self.editor.enter_edit_mode();
self.debug_message =
"โ๏ธ INSERT MODE - Cursor: Steady Bar | - Testing advanced pattern validation"
.to_string();
}
fn enter_append_mode(&mut self) {
self.editor.enter_append_mode();
self.debug_message =
"โ๏ธ INSERT (append) - Cursor: Steady Bar | - Advanced patterns active".to_string();
}
fn exit_edit_mode(&mut self) {
self.editor.exit_edit_mode();
self.debug_message = "๐ NORMAL MODE - Cursor: Steady Block โ".to_string();
self.update_field_validation_status();
}
fn insert_char(&mut self, ch: char) -> anyhow::Result<()> {
let result = self.editor.insert_char(ch);
if result.is_ok() {
let validation_result = self.editor.validate_current_field();
match validation_result {
ValidationResult::Valid => {
self.debug_message = "โ
Character accepted".to_string();
}
ValidationResult::Warning { message } => {
self.debug_message = format!("โ ๏ธ Warning: {message}");
}
ValidationResult::Error { message } => {
self.debug_message = format!("โ Pattern violation: {message}");
}
}
}
result
}
fn delete_backward(&mut self) -> anyhow::Result<()> {
let result = self.editor.delete_backward();
if result.is_ok() {
self.debug_message = "โซ Character deleted".to_string();
}
result
}
fn delete_forward(&mut self) -> anyhow::Result<()> {
let result = self.editor.delete_forward();
if result.is_ok() {
self.debug_message = "โฆ Character deleted".to_string();
}
result
}
fn current_field(&self) -> usize {
self.editor.current_field()
}
fn cursor_position(&self) -> usize {
self.editor.cursor_position()
}
fn mode(&self) -> AppMode {
self.editor.mode()
}
fn current_text(&self) -> &str {
let field_index = self.editor.current_field();
self.editor.data_provider().field_value(field_index)
}
fn data_provider(&self) -> &D {
self.editor.data_provider()
}
fn ui_state(&self) -> &tui_canvas::EditorState {
self.editor.ui_state()
}
fn set_mode(&mut self, mode: AppMode) {
self.editor.set_mode(mode);
}
fn next_field(&mut self) {
if self.editor.next_field() {
self.update_field_validation_status();
self.field_switch_blocked = false;
self.block_reason = None;
} else if let Some(reason) = self.editor.field_switch_block_reason() {
self.field_switch_blocked = true;
self.block_reason = Some(reason.clone());
self.debug_message = format!("๐ซ Cannot move to next field: {reason}");
}
}
fn prev_field(&mut self) {
if self.editor.prev_field() {
self.update_field_validation_status();
self.field_switch_blocked = false;
self.block_reason = None;
} else if let Some(reason) = self.editor.field_switch_block_reason() {
self.field_switch_blocked = true;
self.block_reason = Some(reason.clone());
self.debug_message = format!("๐ซ Cannot move to previous field: {reason}");
}
}
fn set_debug_message(&mut self, msg: String) {
self.debug_message = msg;
}
fn debug_message(&self) -> &str {
&self.debug_message
}
fn update_field_validation_status(&mut self) {
if !self.validation_enabled {
return;
}
let result = self.editor.validate_current_field();
match result {
ValidationResult::Valid => {
self.debug_message = format!(
"Field {}: โ
Pattern valid",
self.editor.current_field() + 1
);
}
ValidationResult::Warning { message } => {
self.debug_message =
format!("Field {}: โ ๏ธ {}", self.editor.current_field() + 1, message);
}
ValidationResult::Error { message } => {
self.debug_message =
format!("Field {}: โ {}", self.editor.current_field() + 1, message);
}
}
}
fn get_validation_status(&self) -> String {
if !self.validation_enabled {
return "โ DISABLED".to_string();
}
if self.field_switch_blocked {
return "๐ซ SWITCH BLOCKED".to_string();
}
let summary = self.editor.validation_summary();
if summary.has_errors() {
format!("โ {} ERRORS", summary.error_fields)
} else if summary.has_warnings() {
format!("โ ๏ธ {} WARNINGS", summary.warning_fields)
} else if summary.validated_fields > 0 {
format!("โ
{} VALID", summary.valid_fields)
} else {
"๐ READY".to_string()
}
}
}
struct AdvancedPatternData {
fields: Vec<(String, String)>,
}
impl AdvancedPatternData {
fn new() -> Self {
Self {
fields: vec![
("๐ Time (HH:MM) - 24hr format".to_string(), "".to_string()),
(
"๐จ Hex Color (#RRGGBB) - Web colors".to_string(),
"".to_string(),
),
(
"๐ IPv4 (XXX.XXX.XXX.XXX) - Network address".to_string(),
"".to_string(),
),
(
"๐ท๏ธ Product Code (ABC-123-XYZ) - Mixed format".to_string(),
"".to_string(),
),
(
"๐
Date Code (2024W15) - Year + Week".to_string(),
"".to_string(),
),
(
"๐ข Binary (101010) - Only 0s and 1s".to_string(),
"".to_string(),
),
(
"๐ฏ Complex ID (A1-B2C-3D4E) - Multi-rule".to_string(),
"".to_string(),
),
(
"๐ Custom Pattern - Advanced logic".to_string(),
"".to_string(),
),
],
}
}
}
impl DataProvider for AdvancedPatternData {
fn field_count(&self) -> usize {
self.fields.len()
}
fn field_name(&self, index: usize) -> &str {
&self.fields[index].0
}
fn field_value(&self, index: usize) -> &str {
&self.fields[index].1
}
fn set_field_value(&mut self, index: usize, value: String) {
self.fields[index].1 = value;
}
fn validation_config(&self, field_index: usize) -> Option<ValidationConfig> {
match field_index {
0 => {
let time_pattern = PatternFilters::new()
.add_filter(PositionFilter::new(
PositionRange::Multiple(vec![0, 1, 3, 4]), CharacterFilter::Numeric,
))
.add_filter(PositionFilter::new(
PositionRange::Single(2), CharacterFilter::Exact(':'),
));
Some(
ValidationConfigBuilder::new()
.with_pattern_filters(time_pattern)
.with_max_length(5) .build(),
)
}
1 => {
let hex_digits = vec![
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
'a', 'b', 'c', 'd', 'e', 'f',
];
let hex_color_pattern = PatternFilters::new()
.add_filter(PositionFilter::new(
PositionRange::Single(0), CharacterFilter::Exact('#'),
))
.add_filter(PositionFilter::new(
PositionRange::Range(1, 6), CharacterFilter::OneOf(hex_digits),
));
Some(
ValidationConfigBuilder::new()
.with_pattern_filters(hex_color_pattern)
.with_max_length(7) .build(),
)
}
2 => {
let ipv4_pattern = PatternFilters::new()
.add_filter(PositionFilter::new(
PositionRange::Multiple(vec![3, 7, 11]), CharacterFilter::Exact('.'),
))
.add_filter(PositionFilter::new(
PositionRange::Multiple(vec![0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14]), CharacterFilter::Numeric,
));
Some(
ValidationConfigBuilder::new()
.with_pattern_filters(ipv4_pattern)
.with_max_length(15) .build(),
)
}
3 => {
let product_code_pattern = PatternFilters::new()
.add_filter(PositionFilter::new(
PositionRange::Range(0, 2), CharacterFilter::Alphabetic,
))
.add_filter(PositionFilter::new(
PositionRange::Multiple(vec![3, 7]), CharacterFilter::Exact('-'),
))
.add_filter(PositionFilter::new(
PositionRange::Range(4, 6), CharacterFilter::Numeric,
))
.add_filter(PositionFilter::new(
PositionRange::Range(8, 10), CharacterFilter::Alphabetic,
));
Some(
ValidationConfigBuilder::new()
.with_pattern_filters(product_code_pattern)
.with_max_length(11) .build(),
)
}
4 => {
let date_code_pattern = PatternFilters::new()
.add_filter(PositionFilter::new(
PositionRange::Range(0, 3), CharacterFilter::Numeric,
))
.add_filter(PositionFilter::new(
PositionRange::Single(4), CharacterFilter::Exact('W'),
))
.add_filter(PositionFilter::new(
PositionRange::From(5), CharacterFilter::Numeric,
));
Some(
ValidationConfigBuilder::new()
.with_pattern_filters(date_code_pattern)
.with_max_length(7) .build(),
)
}
5 => {
let binary_pattern = PatternFilters::new().add_filter(PositionFilter::new(
PositionRange::From(0), CharacterFilter::OneOf(vec!['0', '1']),
));
Some(
ValidationConfigBuilder::new()
.with_pattern_filters(binary_pattern)
.with_max_length(16) .build(),
)
}
6 => {
let complex_id_pattern = PatternFilters::new()
.add_filter(PositionFilter::new(
PositionRange::Multiple(vec![0, 3, 6, 8]), CharacterFilter::Alphabetic,
))
.add_filter(PositionFilter::new(
PositionRange::Multiple(vec![1, 4, 7, 9]), CharacterFilter::Numeric,
))
.add_filter(PositionFilter::new(
PositionRange::Multiple(vec![2, 5]), CharacterFilter::Exact('-'),
))
.add_filter(PositionFilter::new(
PositionRange::Single(5), CharacterFilter::Alphabetic, ));
Some(
ValidationConfigBuilder::new()
.with_pattern_filters(complex_id_pattern)
.with_max_length(10) .build(),
)
}
7 => {
let custom_pattern = PatternFilters::new().add_filter(PositionFilter::new(
PositionRange::From(0),
CharacterFilter::Custom(Arc::new(|c| {
let vowels = ['a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'];
c.is_alphabetic()
})),
));
Some(
ValidationConfigBuilder::new()
.with_pattern_filters(custom_pattern)
.with_max_length(12) .build(),
)
}
_ => None,
}
}
}
fn handle_key_press(
key: KeyCode,
modifiers: KeyModifiers,
editor: &mut AdvancedPatternTextForm<AdvancedPatternData>,
) -> anyhow::Result<bool> {
let mode = editor.mode();
if (key == KeyCode::Char('q') && modifiers.contains(KeyModifiers::CONTROL))
|| (key == KeyCode::Char('c') && modifiers.contains(KeyModifiers::CONTROL))
|| key == KeyCode::F(10)
{
return Ok(false);
}
match (mode, key, modifiers) {
(AppMode::Nor, KeyCode::Char('i'), _) => {
editor.enter_edit_mode();
editor.clear_command_buffer();
}
(AppMode::Nor, KeyCode::Char('a'), _) => {
editor.enter_append_mode();
editor.clear_command_buffer();
}
(AppMode::Nor, KeyCode::Char('A'), _) => {
editor.move_line_end();
editor.enter_edit_mode();
editor.clear_command_buffer();
}
(_, KeyCode::Esc, _) => {
if mode == AppMode::Ins {
editor.exit_edit_mode();
} else {
editor.clear_command_buffer();
}
}
(AppMode::Nor, KeyCode::F(1), _) => {
editor.toggle_validation();
}
(AppMode::Nor, KeyCode::Char('h'), _) | (AppMode::Nor, KeyCode::Left, _) => {
editor.move_left();
editor.clear_command_buffer();
}
(AppMode::Nor, KeyCode::Char('l'), _) | (AppMode::Nor, KeyCode::Right, _) => {
editor.move_right();
editor.clear_command_buffer();
}
(AppMode::Nor, KeyCode::Char('j'), _) | (AppMode::Nor, KeyCode::Down, _) => {
editor.move_down();
editor.clear_command_buffer();
}
(AppMode::Nor, KeyCode::Char('k'), _) | (AppMode::Nor, KeyCode::Up, _) => {
editor.move_up();
editor.clear_command_buffer();
}
(AppMode::Ins, KeyCode::Left, _) => {
editor.move_left();
}
(AppMode::Ins, KeyCode::Right, _) => {
editor.move_right();
}
(AppMode::Ins, KeyCode::Up, _) => {
editor.move_up();
}
(AppMode::Ins, KeyCode::Down, _) => {
editor.move_down();
}
(AppMode::Ins, KeyCode::Backspace, _) => {
editor.delete_backward()?;
}
(AppMode::Ins, KeyCode::Delete, _) => {
editor.delete_forward()?;
}
(_, KeyCode::Tab, _) => {
editor.next_field();
}
(_, KeyCode::BackTab, _) => {
editor.prev_field();
}
(AppMode::Ins, KeyCode::Char(c), m) if !m.contains(KeyModifiers::CONTROL) => {
editor.insert_char(c)?;
}
(AppMode::Nor, KeyCode::Char('?'), _) => {
let summary = editor.editor.validation_summary();
editor.set_debug_message(format!(
"Field {}/{}, Pos {}, Mode: {:?}, Advanced patterns: {} configured",
editor.current_field() + 1,
editor.data_provider().field_count(),
editor.cursor_position(),
editor.mode(),
summary.total_fields
));
}
_ => {
if editor.has_pending_command() {
editor.clear_command_buffer();
editor.set_debug_message("Invalid command sequence".to_string());
}
}
}
Ok(true)
}
fn run_app<B: Backend<Error = io::Error>>(
terminal: &mut Terminal<B>,
mut editor: AdvancedPatternTextForm<AdvancedPatternData>,
) -> io::Result<()> {
loop {
terminal.draw(|f| ui(f, &editor))?;
if let Event::Key(key) = event::read()? {
match handle_key_press(key.code, key.modifiers, &mut editor) {
Ok(should_continue) => {
if !should_continue {
break;
}
}
Err(e) => {
editor.set_debug_message(format!("Error: {e}"));
}
}
}
}
Ok(())
}
fn ui(f: &mut Frame, editor: &AdvancedPatternTextForm<AdvancedPatternData>) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(8), Constraint::Length(15)])
.split(f.area());
render_canvas_default(f, chunks[0], &editor.editor);
render_advanced_validation_status(f, chunks[1], editor);
}
fn render_advanced_validation_status(
f: &mut Frame,
area: Rect,
editor: &AdvancedPatternTextForm<AdvancedPatternData>,
) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(3), Constraint::Length(5), Constraint::Length(7), ])
.split(area);
let mode_text = match editor.mode() {
AppMode::Ins => "INSERT | (bar cursor)",
AppMode::Nor => "NORMAL โ (block cursor)",
_ => "NORMAL โ (block cursor)",
};
let validation_status = editor.get_validation_status();
let status_text = format!(
"-- {} -- {} | Advanced Patterns: {}",
mode_text,
editor.debug_message(),
validation_status
);
let status = Paragraph::new(Line::from(Span::raw(status_text))).block(
Block::default()
.borders(Borders::ALL)
.title("๐ Advanced Pattern Validation"),
);
f.render_widget(status, chunks[0]);
let summary = editor.editor.validation_summary();
let field_info = match editor.current_field() {
0 => "Time format (HH:MM) - Tests exact chars + numeric ranges",
1 => "Hex color (#RRGGBB) - Tests OneOf filter with case insensitive",
2 => "IPv4 address - Tests complex dot positioning",
3 => "Product code (ABC-123-XYZ) - Tests section-based patterns",
4 => "Date code (2024W15) - Tests From position filtering",
5 => "Binary input - Tests limited character set (0,1 only)",
6 => "Complex ID - Tests overlapping/conflicting rules",
7 => "Custom pattern - Tests advanced custom validation logic",
_ => "Unknown field",
};
let summary_text = if editor.validation_enabled {
format!(
"๐ Advanced Pattern Summary: {} fields with complex rules\n\
Current Field: {}\n\
โ
Valid: {} โ ๏ธ Warnings: {} โ Errors: {} ๐ Progress: {:.0}%\n\
๐ฏ Pattern Focus: {}",
summary.total_fields,
editor.current_field() + 1,
summary.valid_fields,
summary.warning_fields,
summary.error_fields,
summary.completion_percentage() * 100.0,
field_info
)
} else {
"โ Advanced pattern validation is DISABLED\nPress F1 to enable and see the magic!"
.to_string()
};
let summary_style = if summary.has_errors() {
Style::default().fg(Color::Red)
} else if summary.has_warnings() {
Style::default().fg(Color::Yellow)
} else {
Style::default().fg(Color::Green)
};
let validation_summary = Paragraph::new(summary_text)
.block(
Block::default()
.borders(Borders::ALL)
.title("๐ฏ Advanced Pattern Analysis"),
)
.style(summary_style)
.wrap(Wrap { trim: true });
f.render_widget(validation_summary, chunks[1]);
let help_text = match editor.mode() {
AppMode::Nor => {
"๐ ADVANCED PATTERN SHOWCASE - Each field demonstrates different edge cases!\n\
๐ Time: Numeric+exact chars ๐จ Hex: OneOf with case-insensitive ๐ IPv4: Complex positioning\n\
๐ท๏ธ Product: Multi-section rules ๐
Date: From-position filtering ๐ข Binary: Limited charset\n\
๐ฏ Complex: Overlapping rules ๐ Custom: Advanced logic functions\n\
\n\
Movement: hjkl/arrows=move, Tab/Shift+Tab=fields, i/a=insert, F1=toggle, ?=info"
}
AppMode::Ins => {
"โ๏ธ INSERT MODE - Testing advanced pattern validation!\n\
Each character is validated against complex rules in real-time\n\
Try entering invalid characters to see detailed error messages\n\
arrows=move, Backspace/Del=delete, Esc=normal, Tab=next field"
}
_ => "๐ Advanced Pattern Validation Active!",
};
let help = Paragraph::new(help_text)
.block(
Block::default()
.borders(Borders::ALL)
.title("๐ฏ Advanced Pattern Commands & Info"),
)
.style(Style::default().fg(Color::Gray))
.wrap(Wrap { trim: true });
f.render_widget(help, chunks[2]);
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("๐ Canvas Advanced Pattern Validation Demo");
println!("โ
validation feature: ENABLED");
println!("โ
gui feature: ENABLED");
println!("โ
cursor-style feature: ENABLED");
println!("๐ฏ Advanced pattern filtering: ACTIVE");
println!("๐งช Edge cases and complex patterns: READY");
println!("๐ก Each field showcases different validation capabilities!");
println!();
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let data = AdvancedPatternData::new();
let mut editor = AdvancedPatternTextForm::new(data);
editor.set_mode(AppMode::Nor);
CursorManager::update_for_mode(AppMode::Nor)?;
let res = run_app(&mut terminal, editor);
CursorManager::reset()?;
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
println!("{err:?}");
}
println!("๐ Advanced pattern validation demo completed!");
println!("๐ฏ Hope you enjoyed seeing all the edge cases in action!");
Ok(())
}