cleared codebase

This commit is contained in:
Priec
2025-08-13 01:22:50 +02:00
parent 2b16a80ef8
commit 3227d341ed
5 changed files with 156 additions and 118 deletions

View File

@@ -88,42 +88,27 @@ pub fn render_canvas_with_highlight<T: CanvasTheme, D: DataProvider>(
highlight_state, highlight_state,
editor.display_cursor_position(), // Use display cursor position for masks editor.display_cursor_position(), // Use display cursor position for masks
false, // TODO: track unsaved changes in editor false, // TODO: track unsaved changes in editor
|i| { // Closures for getting display values and overrides
// Get display value for field i using editor logic (Feature 4 + masks) #[cfg(feature = "validation")]
#[cfg(feature = "validation")] |field_idx| editor.display_text_for_field(field_idx),
{ #[cfg(not(feature = "validation"))]
editor.display_text_for_field(i) |field_idx| data_provider.field_value(field_idx).to_string(),
} // Closure for checking display overrides
#[cfg(not(feature = "validation"))] #[cfg(feature = "validation")]
{ |field_idx| {
data_provider.field_value(i).to_string() editor.ui_state().validation_state().get_field_config(field_idx)
} .map(|cfg| {
let has_formatter = cfg.custom_formatter.is_some();
let has_mask = cfg.display_mask.is_some();
has_formatter || has_mask
})
.unwrap_or(false)
}, },
|i| { #[cfg(not(feature = "validation"))]
// Check if field has display override (custom formatter or mask) |_field_idx| false,
#[cfg(feature = "validation")] // Closure for providing completion
{ |field_idx| {
editor.ui_state().validation_state().get_field_config(i) if field_idx == current_field_idx {
.map(|cfg| {
// Formatter takes precedence; if present, it's a display override
#[allow(unused_mut)]
let mut has_override = false;
#[cfg(feature = "validation")]
{
has_override = cfg.custom_formatter.is_some();
}
has_override || cfg.display_mask.is_some()
})
.unwrap_or(false)
}
#[cfg(not(feature = "validation"))]
{
false
}
},
// NEW: provide completion for the active field
|i| {
if i == current_field_idx {
active_completion.clone() active_completion.clone()
} else { } else {
None None
@@ -269,7 +254,8 @@ where
{ {
let mut active_field_input_rect = None; let mut active_field_input_rect = None;
for (i, input) in inputs.iter().enumerate() { // FIX: Iterate over indices only since we never use the input values directly
for i in 0..inputs.len() {
let is_active = i == *current_field_idx; let is_active = i == *current_field_idx;
let typed_text = get_display_value(i); let typed_text = get_display_value(i);
@@ -353,7 +339,7 @@ fn apply_characterwise_highlighting<'a, T: CanvasTheme>(
current_cursor_pos: usize, current_cursor_pos: usize,
anchor: &(usize, usize), anchor: &(usize, usize),
theme: &T, theme: &T,
is_active: bool, _is_active: bool,
) -> Line<'a> { ) -> Line<'a> {
let (anchor_field, anchor_char) = *anchor; let (anchor_field, anchor_char) = *anchor;
let start_field = min(anchor_field, *current_field_idx); let start_field = min(anchor_field, *current_field_idx);
@@ -456,7 +442,7 @@ fn apply_linewise_highlighting<'a, T: CanvasTheme>(
current_field_idx: &usize, current_field_idx: &usize,
anchor_line: &usize, anchor_line: &usize,
theme: &T, theme: &T,
is_active: bool, _is_active: bool,
) -> Line<'a> { ) -> Line<'a> {
let start_field = min(*anchor_line, *current_field_idx); let start_field = min(*anchor_line, *current_field_idx);
let end_field = max(*anchor_line, *current_field_idx); let end_field = max(*anchor_line, *current_field_idx);
@@ -487,7 +473,7 @@ fn set_cursor_position(
field_rect: Rect, field_rect: Rect,
text: &str, text: &str,
current_cursor_pos: usize, current_cursor_pos: usize,
has_display_override: bool, _has_display_override: bool,
) { ) {
// Sum display widths of the first current_cursor_pos characters // Sum display widths of the first current_cursor_pos characters
let mut cols: u16 = 0; let mut cols: u16 = 0;

View File

@@ -25,7 +25,7 @@ pub trait ComputedProvider {
/// Get list of field dependencies for optimization. /// Get list of field dependencies for optimization.
/// If field A depends on fields [1, 3], only recompute A when fields 1 or 3 change. /// If field A depends on fields [1, 3], only recompute A when fields 1 or 3 change.
/// Default: depend on all fields (always recompute) with a reasonable upper bound. /// Default: depend on all fields (always recompute) with a reasonable upper bound.
fn field_dependencies(&self, field_index: usize) -> Vec<usize> { fn field_dependencies(&self, _field_index: usize) -> Vec<usize> {
(0..100).collect() (0..100).collect()
} }
} }

View File

@@ -19,33 +19,33 @@ pub trait DataProvider {
fn set_field_value(&mut self, index: usize, value: String); fn set_field_value(&mut self, index: usize, value: String);
/// Check if field supports suggestions (optional) /// Check if field supports suggestions (optional)
fn supports_suggestions(&self, field_index: usize) -> bool { fn supports_suggestions(&self, _field_index: usize) -> bool {
false false
} }
/// Get display value (for password masking, etc.) - optional /// Get display value (for password masking, etc.) - optional
fn display_value(&self, index: usize) -> Option<&str> { fn display_value(&self, _index: usize) -> Option<&str> {
None // Default: use actual value None // Default: use actual value
} }
/// Get validation configuration for a field (optional) /// Get validation configuration for a field (optional)
/// Only available when the 'validation' feature is enabled /// Only available when the 'validation' feature is enabled
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
fn validation_config(&self, field_index: usize) -> Option<crate::validation::ValidationConfig> { fn validation_config(&self, _field_index: usize) -> Option<crate::validation::ValidationConfig> {
None None
} }
/// Check if field is computed (display-only, skip in navigation) /// Check if field is computed (display-only, skip in navigation)
/// Default: not computed /// Default: not computed
#[cfg(feature = "computed")] #[cfg(feature = "computed")]
fn is_computed_field(&self, field_index: usize) -> bool { fn is_computed_field(&self, _field_index: usize) -> bool {
false false
} }
/// Get computed field value if this is a computed field. /// Get computed field value if this is a computed field.
/// Returns None for regular fields. Default: not computed. /// Returns None for regular fields. Default: not computed.
#[cfg(feature = "computed")] #[cfg(feature = "computed")]
fn computed_field_value(&self, field_index: usize) -> Option<String> { fn computed_field_value(&self, _field_index: usize) -> Option<String> {
None None
} }
} }

View File

@@ -4,13 +4,9 @@
#[cfg(feature = "cursor-style")] #[cfg(feature = "cursor-style")]
use crate::canvas::CursorManager; use crate::canvas::CursorManager;
use anyhow::Result; use anyhow::Result;
use crate::canvas::state::EditorState; use crate::canvas::state::EditorState;
use crate::{DataProvider, SuggestionItem}; use crate::{DataProvider, SuggestionItem};
#[cfg(feature = "suggestions")]
use crate::SuggestionsProvider;
use crate::canvas::modes::AppMode; use crate::canvas::modes::AppMode;
use crate::canvas::state::SelectionState; use crate::canvas::state::SelectionState;
@@ -49,8 +45,9 @@ impl<D: DataProvider> FormEditor<D> {
fn byte_to_char_index(s: &str, byte_idx: usize) -> usize { fn byte_to_char_index(s: &str, byte_idx: usize) -> usize {
s[..byte_idx].chars().count() s[..byte_idx].chars().count()
} }
pub fn new(data_provider: D) -> Self { pub fn new(data_provider: D) -> Self {
let mut editor = Self { let editor = Self {
ui_state: EditorState::new(), ui_state: EditorState::new(),
data_provider, data_provider,
suggestions: Vec::new(), suggestions: Vec::new(),
@@ -61,10 +58,14 @@ impl<D: DataProvider> FormEditor<D> {
// Initialize validation configurations if validation feature is enabled // Initialize validation configurations if validation feature is enabled
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
{ {
let mut editor = editor;
editor.initialize_validation(); editor.initialize_validation();
editor
}
#[cfg(not(feature = "validation"))]
{
editor
} }
editor
} }
/// Get current field text (convenience method) /// Get current field text (convenience method)
@@ -160,7 +161,7 @@ impl<D: DataProvider> FormEditor<D> {
if matches!(self.ui_state.current_mode, AppMode::Edit) { if matches!(self.ui_state.current_mode, AppMode::Edit) {
return raw.to_string(); return raw.to_string();
} }
if let Some((formatted, mapper, warning)) = cfg.run_custom_formatter(raw) { if let Some((formatted, _mapper, _warning)) = cfg.run_custom_formatter(raw) {
return formatted; return formatted;
} }
} }
@@ -179,11 +180,6 @@ impl<D: DataProvider> FormEditor<D> {
&self.ui_state &self.ui_state
} }
/// Mutable access to UI state for internal crate use only.
pub(crate) fn ui_state_mut(&mut self) -> &mut EditorState {
&mut self.ui_state
}
/// Open the suggestions UI for `field_index` (UI-only; does not fetch). /// Open the suggestions UI for `field_index` (UI-only; does not fetch).
pub fn open_suggestions(&mut self, field_index: usize) { pub fn open_suggestions(&mut self, field_index: usize) {
self.ui_state.open_suggestions(field_index); self.ui_state.open_suggestions(field_index);
@@ -257,7 +253,7 @@ impl<D: DataProvider> FormEditor<D> {
return raw.to_string(); return raw.to_string();
} }
// Not editing -> formatted // Not editing -> formatted
if let Some((formatted, mapper, warning)) = cfg.run_custom_formatter(raw) { if let Some((formatted, _mapper, _warning)) = cfg.run_custom_formatter(raw) {
return formatted; return formatted;
} }
} }
@@ -305,7 +301,6 @@ impl<D: DataProvider> FormEditor<D> {
// =================================================================== // ===================================================================
/// Centralized field transition logic /// Centralized field transition logic
#[cfg_attr(not(feature = "validation"), allow(unused_variables))]
pub fn transition_to_field(&mut self, new_field: usize) -> Result<()> { pub fn transition_to_field(&mut self, new_field: usize) -> Result<()> {
let field_count = self.data_provider.field_count(); let field_count = self.data_provider.field_count();
if field_count == 0 { if field_count == 0 {
@@ -314,8 +309,11 @@ impl<D: DataProvider> FormEditor<D> {
let prev_field = self.ui_state.current_field; let prev_field = self.ui_state.current_field;
// 1. Bounds check // FIX 2: Only mut when computed feature actually modifies it
#[cfg(feature = "computed")]
let mut target_field = new_field.min(field_count - 1); let mut target_field = new_field.min(field_count - 1);
#[cfg(not(feature = "computed"))]
let target_field = new_field.min(field_count - 1);
// 2. Computed field skipping // 2. Computed field skipping
#[cfg(feature = "computed")] #[cfg(feature = "computed")]
@@ -432,8 +430,20 @@ impl<D: DataProvider> FormEditor<D> {
return Ok(()); // Ignore in non-edit modes return Ok(()); // Ignore in non-edit modes
} }
// Variables are only declared when the features that use them are enabled
#[cfg(feature = "validation")]
let field_index = self.ui_state.current_field; let field_index = self.ui_state.current_field;
#[cfg(feature = "validation")]
let raw_cursor_pos = self.ui_state.cursor_pos; let raw_cursor_pos = self.ui_state.cursor_pos;
#[cfg(feature = "validation")]
let current_raw_text = self.data_provider.field_value(field_index);
// When validation is disabled, we declare these variables differently
#[cfg(not(feature = "validation"))]
let field_index = self.ui_state.current_field;
#[cfg(not(feature = "validation"))]
let raw_cursor_pos = self.ui_state.cursor_pos;
#[cfg(not(feature = "validation"))]
let current_raw_text = self.data_provider.field_value(field_index); let current_raw_text = self.data_provider.field_value(field_index);
// Mask gate: reject input that doesn't fit the mask at current position // Mask gate: reject input that doesn't fit the mask at current position
@@ -566,7 +576,12 @@ impl<D: DataProvider> FormEditor<D> {
/// Restore left and right movement within the current field /// Restore left and right movement within the current field
/// Move cursor left within current field /// Move cursor left within current field
pub fn move_left(&mut self) -> Result<()> { pub fn move_left(&mut self) -> Result<()> {
// FIX 3: Only mut when validation feature modifies it
#[cfg(feature = "validation")]
let mut moved = false; let mut moved = false;
#[cfg(not(feature = "validation"))]
let moved = false;
// Try mask-aware movement if validation/mask config exists // Try mask-aware movement if validation/mask config exists
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
{ {
@@ -600,11 +615,16 @@ impl<D: DataProvider> FormEditor<D> {
/// Move cursor right within current field /// Move cursor right within current field
pub fn move_right(&mut self) -> Result<()> { pub fn move_right(&mut self) -> Result<()> {
// FIX 4: Only mut when validation feature modifies it
#[cfg(feature = "validation")]
let mut moved = false; let mut moved = false;
let field_index = self.ui_state.current_field; #[cfg(not(feature = "validation"))]
let moved = false;
// Try mask-aware movement if mask is configured for this field // Try mask-aware movement if mask is configured for this field
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
{ {
let field_index = self.ui_state.current_field;
if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) { if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
if let Some(mask) = &cfg.display_mask { if let Some(mask) = &cfg.display_mask {
let display_pos = mask.raw_pos_to_display_pos(self.ui_state.cursor_pos); let display_pos = mask.raw_pos_to_display_pos(self.ui_state.cursor_pos);
@@ -771,7 +791,7 @@ impl<D: DataProvider> FormEditor<D> {
pub fn current_formatter_warning(&self) -> Option<String> { pub fn current_formatter_warning(&self) -> Option<String> {
let idx = self.ui_state.current_field; let idx = self.ui_state.current_field;
if let Some(cfg) = self.ui_state.validation.get_field_config(idx) { if let Some(cfg) = self.ui_state.validation.get_field_config(idx) {
if let Some((fmt, mapper, warn)) = cfg.run_custom_formatter(self.current_text()) { if let Some((_fmt, _mapper, warn)) = cfg.run_custom_formatter(self.current_text()) {
return warn; return warn;
} }
} }
@@ -811,38 +831,45 @@ impl<D: DataProvider> FormEditor<D> {
/// Begin suggestions loading for a field (UI updates immediately, no fetch) /// Begin suggestions loading for a field (UI updates immediately, no fetch)
/// This opens the dropdown with "Loading..." state instantly /// This opens the dropdown with "Loading..." state instantly
/// ///
/// The caller is responsible for fetching suggestions and calling /// The caller is responsible for fetching suggestions and calling
/// `apply_suggestions_result()` when ready. /// `apply_suggestions_result()` when ready.
#[cfg(feature = "suggestions")]
pub fn start_suggestions(&mut self, field_index: usize) -> Option<String> { pub fn start_suggestions(&mut self, field_index: usize) -> Option<String> {
if !self.data_provider.supports_suggestions(field_index) { if !self.data_provider.supports_suggestions(field_index) {
return None; return None;
} }
let query = self.current_text().to_string(); let query = self.current_text().to_string();
// Open suggestions UI immediately - user sees dropdown right away // Open suggestions UI immediately - user sees dropdown right away
self.ui_state.open_suggestions(field_index); self.ui_state.open_suggestions(field_index);
// ADD THIS LINE - mark as loading so UI shows "Loading..." // ADD THIS LINE - mark as loading so UI shows "Loading..."
self.ui_state.suggestions.is_loading = true; self.ui_state.suggestions.is_loading = true;
// Store the query we're loading for (prevents stale results) // Store the query we're loading for (prevents stale results)
self.ui_state.suggestions.active_query = Some(query.clone()); self.ui_state.suggestions.active_query = Some(query.clone());
// Clear any old suggestions // Clear any old suggestions
self.suggestions.clear(); self.suggestions.clear();
// Return the query so caller knows what to fetch // Return the query so caller knows what to fetch
Some(query) Some(query)
} }
#[cfg(not(feature = "suggestions"))]
pub fn start_suggestions(&mut self, _field_index: usize) -> Option<String> {
None
}
/// Apply fetched suggestions results /// Apply fetched suggestions results
/// ///
/// This will ignore stale results if the field or query has changed since /// This will ignore stale results if the field or query has changed since
/// `start_suggestions()` was called. /// `start_suggestions()` was called.
/// ///
/// Returns `true` if results were applied, `false` if they were stale/ignored. /// Returns `true` if results were applied, `false` if they were stale/ignored.
#[cfg(feature = "suggestions")]
pub fn apply_suggestions_result( pub fn apply_suggestions_result(
&mut self, &mut self,
field_index: usize, field_index: usize,
@@ -874,9 +901,20 @@ impl<D: DataProvider> FormEditor<D> {
true true
} }
#[cfg(not(feature = "suggestions"))]
pub fn apply_suggestions_result(
&mut self,
_field_index: usize,
_query: &str,
_results: Vec<SuggestionItem>,
) -> bool {
false
}
/// Check if there's an active suggestions query waiting for results /// Check if there's an active suggestions query waiting for results
/// ///
/// Returns (field_index, query) if suggestions are loading, None otherwise. /// Returns (field_index, query) if suggestions are loading, None otherwise.
#[cfg(feature = "suggestions")]
pub fn pending_suggestions_query(&self) -> Option<(usize, String)> { pub fn pending_suggestions_query(&self) -> Option<(usize, String)> {
if self.ui_state.suggestions.is_loading { if self.ui_state.suggestions.is_loading {
if let (Some(field), Some(query)) = ( if let (Some(field), Some(query)) = (
@@ -889,6 +927,11 @@ impl<D: DataProvider> FormEditor<D> {
None None
} }
#[cfg(not(feature = "suggestions"))]
pub fn pending_suggestions_query(&self) -> Option<(usize, String)> {
None
}
/// Cancel any pending suggestions (useful for cleanup) /// Cancel any pending suggestions (useful for cleanup)
pub fn cancel_suggestions(&mut self) { pub fn cancel_suggestions(&mut self) {
self.close_suggestions(); self.close_suggestions();
@@ -932,7 +975,7 @@ impl<D: DataProvider> FormEditor<D> {
// Validate the new content if validation is enabled // Validate the new content if validation is enabled
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
{ {
let validation_result = self.ui_state.validation.validate_field_content( let _validation_result = self.ui_state.validation.validate_field_content(
field_index, field_index,
&suggestion.value_to_store, &suggestion.value_to_store,
); );
@@ -1197,8 +1240,12 @@ impl<D: DataProvider> FormEditor<D> {
current_text.replace_range(start..end, ""); current_text.replace_range(start..end, "");
self.data_provider.set_field_value(field_index, current_text.clone()); self.data_provider.set_field_value(field_index, current_text.clone());
// Always run reposition logic // FIX 5: Only mut when validation feature might modify it
#[cfg(feature = "validation")]
let mut target_cursor = new_cursor; let mut target_cursor = new_cursor;
#[cfg(not(feature = "validation"))]
let target_cursor = new_cursor;
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
{ {
if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) { if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
@@ -1240,7 +1287,12 @@ impl<D: DataProvider> FormEditor<D> {
current_text.replace_range(start..end, ""); current_text.replace_range(start..end, "");
self.data_provider.set_field_value(field_index, current_text.clone()); self.data_provider.set_field_value(field_index, current_text.clone());
// FIX 6: Only mut when validation feature might modify it
#[cfg(feature = "validation")]
let mut target_cursor = self.ui_state.cursor_pos; let mut target_cursor = self.ui_state.cursor_pos;
#[cfg(not(feature = "validation"))]
let target_cursor = self.ui_state.cursor_pos;
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
{ {
if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) { if let Some(cfg) = self.ui_state.validation.get_field_config(field_index) {
@@ -1349,22 +1401,6 @@ impl<D: DataProvider> FormEditor<D> {
// HELPER METHODS // HELPER METHODS
// =================================================================== // ===================================================================
/// Clamp cursor position to valid bounds for current field and mode
fn clamp_cursor_to_current_field(&mut self) {
let current_text = self.current_text();
let is_edit_mode = self.ui_state.current_mode == AppMode::Edit;
use crate::canvas::actions::movement::line::safe_cursor_position;
let safe_pos = safe_cursor_position(
current_text,
self.ui_state.ideal_cursor_column,
is_edit_mode
);
self.ui_state.cursor_pos = safe_pos;
}
/// Set the value of the current field /// Set the value of the current field
pub fn set_current_field_value(&mut self, value: String) { pub fn set_current_field_value(&mut self, value: String) {
let field_index = self.ui_state.current_field; let field_index = self.ui_state.current_field;
@@ -1376,7 +1412,7 @@ impl<D: DataProvider> FormEditor<D> {
// Validate the new content if validation is enabled // Validate the new content if validation is enabled
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
{ {
let validation_result = self.ui_state.validation.validate_field_content( let _validation_result = self.ui_state.validation.validate_field_content(
field_index, field_index,
&value, &value,
); );
@@ -1396,7 +1432,7 @@ impl<D: DataProvider> FormEditor<D> {
// Validate the new content if validation is enabled // Validate the new content if validation is enabled
#[cfg(feature = "validation")] #[cfg(feature = "validation")]
{ {
let validation_result = self.ui_state.validation.validate_field_content( let _validation_result = self.ui_state.validation.validate_field_content(
field_index, field_index,
&value, &value,
); );
@@ -1474,15 +1510,14 @@ impl<D: DataProvider> FormEditor<D> {
} }
/// Cleanup cursor style (call this when shutting down) /// Cleanup cursor style (call this when shutting down)
#[cfg(feature = "cursor-style")]
pub fn cleanup_cursor(&self) -> std::io::Result<()> { pub fn cleanup_cursor(&self) -> std::io::Result<()> {
#[cfg(feature = "cursor-style")] crate::canvas::CursorManager::reset()
{ }
crate::canvas::CursorManager::reset()
} #[cfg(not(feature = "cursor-style"))]
#[cfg(not(feature = "cursor-style"))] pub fn cleanup_cursor(&self) -> std::io::Result<()> {
{ Ok(())
Ok(())
}
} }

View File

@@ -125,39 +125,56 @@ impl CharacterLimits {
position: usize, position: usize,
character: char, character: char,
) -> Option<ValidationResult> { ) -> Option<ValidationResult> {
let current_count = self.count(current_text); // FIX: Actually simulate the insertion at the specified position
let char_count = match self.count_mode { // This makes the `position` parameter essential to the logic
CountMode::Characters => 1,
CountMode::DisplayWidth => {
let char_str = character.to_string();
char_str.width()
},
CountMode::Bytes => character.len_utf8(),
};
let new_count = current_count + char_count;
// 1. Create the new string by inserting the character at the correct position
let mut new_text = String::with_capacity(current_text.len() + character.len_utf8());
let mut chars = current_text.chars();
// Append characters from the original string that come before the insertion point
// We clamp the position to be safe
let clamped_pos = position.min(current_text.chars().count());
for _ in 0..clamped_pos {
if let Some(ch) = chars.next() {
new_text.push(ch);
}
}
// Insert the new character
new_text.push(character);
// Append the rest of the original string
for ch in chars {
new_text.push(ch);
}
// 2. Now perform all validation on the *actual* resulting text
let new_count = self.count(&new_text);
let current_count = self.count(current_text);
// Check max length // Check max length
if let Some(max) = self.max_length { if let Some(max) = self.max_length {
if new_count > max { if new_count > max {
return Some(ValidationResult::error(format!( return Some(ValidationResult::error(format!(
"Character limit exceeded: {}/{}", "Character limit exceeded: {}/{}",
new_count, new_count,
max max
))); )));
} }
// Check warning threshold // Check warning threshold
if let Some(warning_threshold) = self.warning_threshold { if let Some(warning_threshold) = self.warning_threshold {
if new_count >= warning_threshold && current_count < warning_threshold { if new_count >= warning_threshold && current_count < warning_threshold {
return Some(ValidationResult::warning(format!( return Some(ValidationResult::warning(format!(
"Approaching character limit: {}/{}", "Approaching character limit: {}/{}",
new_count, new_count,
max max
))); )));
} }
} }
} }
None // No validation issues None // No validation issues
} }