Skip to main content

ass_editor/core/document/
validation.rs

1//! Script parsing entry points, command execution, and validation
2//!
3//! Hosts the `parse_script_with` callback bridge, the history-recording
4//! `execute_command`, and the lazy-validator integration methods.
5
6use super::EditorDocument;
7use crate::core::errors::{EditorError, Result};
8use crate::core::position::{Position, Range};
9use ass_core::parser::Script;
10
11#[cfg(not(feature = "std"))]
12use alloc::string::{String, ToString};
13
14impl EditorDocument {
15    /// Parse the current document content into a Script
16    ///
17    /// Returns a boxed closure that provides the parsed Script.
18    /// This avoids lifetime issues by ensuring the content outlives the Script.
19    pub fn parse_script_with<F, R>(&self, f: F) -> Result<R>
20    where
21        F: FnOnce(&Script) -> R,
22    {
23        let content = self.text();
24        match Script::parse(&content) {
25            Ok(script) => Ok(f(&script)),
26            Err(e) => Err(EditorError::from(e)),
27        }
28    }
29
30    /// Validate the document content can be parsed as valid ASS
31    /// This is the basic validation that just checks parsing
32    pub fn validate(&self) -> Result<()> {
33        let content = self.text();
34        Script::parse(&content).map_err(EditorError::from)?;
35        Ok(())
36    }
37
38    /// Execute a command with proper history recording
39    ///
40    /// This method ensures that commands are properly recorded in the undo history.
41    /// Use this instead of calling command.execute() directly if you want undo support.
42    ///
43    /// For BatchCommand, this creates a synthetic undo operation that captures
44    /// the aggregate effect of all sub-commands.
45    pub fn execute_command(
46        &mut self,
47        command: &dyn crate::commands::EditorCommand,
48    ) -> Result<crate::commands::CommandResult> {
49        use crate::core::history::Operation;
50
51        // Get the cursor position before
52        let _cursor_before = self.cursor_position();
53
54        // Execute the command
55        let result = command.execute(self)?;
56
57        // Only record if the command changed content
58        if result.content_changed {
59            // Determine the operation based on the result
60            let operation = if let Some(range) = result.modified_range {
61                if range.is_empty() {
62                    // This was an insertion
63                    let inserted_text = self.text_range(Range::new(
64                        range.start,
65                        Position::new(
66                            range.start.offset
67                                + result
68                                    .new_cursor
69                                    .map_or(0, |c| c.offset - range.start.offset),
70                        ),
71                    ))?;
72                    Operation::Insert {
73                        position: range.start,
74                        text: inserted_text,
75                    }
76                } else {
77                    // For batch commands and complex operations, we need to be more careful
78                    // Store enough information to properly undo
79                    // This is a simplified approach - ideally each command would handle its own undo
80                    let cmd_desc = command.description();
81                    if cmd_desc.contains("batch") || cmd_desc.contains("Multiple") {
82                        // For batch operations, we don't have enough info
83                        // Just use a simple insert operation
84                        Operation::Insert {
85                            position: range.start,
86                            text: String::new(),
87                        }
88                    } else {
89                        // For simple operations, use the range info
90                        Operation::Replace {
91                            range,
92                            old_text: String::new(), // We don't have the old text
93                            new_text: self.text_range(range).unwrap_or_default(),
94                        }
95                    }
96                }
97            } else {
98                // No range info - create a generic operation
99                Operation::Insert {
100                    position: Position::new(0),
101                    text: String::new(),
102                }
103            };
104
105            // Update cursor if needed
106            if let Some(new_cursor) = result.new_cursor {
107                self.set_cursor_position(Some(new_cursor));
108            }
109
110            // Record in history
111            self.history
112                .record_operation(operation, command.description().to_string(), &result);
113
114            // Clear validation cache since content changed
115            self.validator.clear_cache();
116        }
117
118        Ok(result)
119    }
120
121    /// Perform comprehensive validation using the LazyValidator
122    /// Returns detailed validation results including warnings and suggestions
123    ///
124    /// Note: Returns a cloned result to avoid borrow checker issues
125    pub fn validate_comprehensive(&mut self) -> Result<crate::utils::validator::ValidationResult> {
126        // Create a temporary document reference for validation
127        // This is needed because we can't pass &self while mutably borrowing validator
128        let temp_doc = EditorDocument::from_content(&self.text())?;
129        let result = self.validator.validate(&temp_doc)?;
130        Ok(result.clone())
131    }
132
133    /// Force revalidation even if cached results exist
134    pub fn force_validate(&mut self) -> Result<crate::utils::validator::ValidationResult> {
135        // Create a temporary document reference for validation
136        let temp_doc = EditorDocument::from_content(&self.text())?;
137        let result = self.validator.force_validate(&temp_doc)?;
138        Ok(result.clone())
139    }
140
141    /// Check if document is valid (quick check using cache if available)
142    pub fn is_valid_cached(&mut self) -> Result<bool> {
143        // Create a temporary document reference for validation
144        let temp_doc = EditorDocument::from_content(&self.text())?;
145        self.validator.is_valid(&temp_doc)
146    }
147
148    /// Get cached validation result without revalidating
149    pub fn validation_result(&self) -> Option<&crate::utils::validator::ValidationResult> {
150        self.validator.cached_result()
151    }
152
153    /// Configure the validator
154    pub fn set_validator_config(&mut self, config: crate::utils::validator::ValidatorConfig) {
155        self.validator.set_config(config);
156    }
157
158    /// Get mutable access to the validator
159    pub fn validator_mut(&mut self) -> &mut crate::utils::validator::LazyValidator {
160        &mut self.validator
161    }
162}