rs_hack/
editor.rs

1use anyhow::{Context, Result};
2use proc_macro2::{LineColumn, Span};
3use syn::{
4    parse_str, File, Item, ItemEnum, ItemStruct,
5    Fields, Field, spanned::Spanned, Arm, ExprMatch, ExprStruct,
6    visit_mut::VisitMut, Expr,
7};
8use quote::ToTokens;
9
10use crate::operations::*;
11use crate::path_resolver::PathResolver;
12use prettyplease;
13
14pub struct RustEditor {
15    content: String,
16    syntax_tree: File,
17    line_offsets: Vec<usize>, // Byte offset for each line start
18}
19
20impl RustEditor {
21    pub fn new(content: &str) -> Result<Self> {
22        let syntax_tree: File = syn::parse_str(content)
23            .context("Failed to parse Rust code")?;
24
25        let line_offsets = Self::compute_line_offsets(content);
26
27        Ok(Self {
28            content: content.to_string(),
29            syntax_tree,
30            line_offsets,
31        })
32    }
33
34    /// Format a field without extra spaces (e.g., "pub name: String" not "pub name : String")
35    fn format_field(field: &Field) -> String {
36        let mut result = String::new();
37
38        // Add visibility
39        if let syn::Visibility::Public(_) = field.vis {
40            result.push_str("pub ");
41        }
42
43        // Add field name
44        if let Some(ident) = &field.ident {
45            result.push_str(&ident.to_string());
46        }
47
48        // Add colon and type (no space before colon)
49        result.push_str(": ");
50
51        // Format type without extra spaces
52        let type_str = field.ty.to_token_stream().to_string();
53        let type_str = type_str.replace(" < ", "<").replace(" >", ">");
54        result.push_str(&type_str);
55
56        result
57    }
58    
59    fn compute_line_offsets(content: &str) -> Vec<usize> {
60        let mut offsets = vec![0];
61        for (i, ch) in content.char_indices() {
62            if ch == '\n' {
63                offsets.push(i + 1);
64            }
65        }
66        offsets
67    }
68
69    /// Find similar field names using Levenshtein distance for fuzzy matching
70    fn find_similar_fields(target: &str, available: &[String]) -> Vec<String> {
71        use strsim::levenshtein;
72
73        let mut scored: Vec<_> = available.iter()
74            .map(|field| (field, levenshtein(target, field)))
75            .filter(|(_, distance)| *distance <= 3)  // Max 3 character difference
76            .collect();
77
78        scored.sort_by_key(|(_, distance)| *distance);
79        scored.into_iter()
80            .take(3)  // Top 3 suggestions
81            .map(|(field, _)| field.to_string())
82            .collect()
83    }
84
85    pub fn apply_operation(&mut self, op: &Operation) -> Result<ModificationResult> {
86        match op {
87            Operation::AddStructField(op) => self.add_struct_field(op),
88            Operation::UpdateStructField(op) => self.update_struct_field(op),
89            Operation::RemoveStructField(op) => self.remove_struct_field(op),
90            Operation::AddStructLiteralField(op) => self.add_struct_literal_field(op),
91            Operation::AddEnumVariant(op) => self.add_enum_variant(op),
92            Operation::UpdateEnumVariant(op) => self.update_enum_variant(op),
93            Operation::RemoveEnumVariant(op) => self.remove_enum_variant(op),
94            Operation::AddMatchArm(op) => self.add_match_arm(op),
95            Operation::UpdateMatchArm(op) => self.update_match_arm(op),
96            Operation::RemoveMatchArm(op) => self.remove_match_arm(op),
97            Operation::AddImplMethod(op) => self.add_impl_method(op),
98            Operation::AddUseStatement(op) => self.add_use_statement(op),
99            Operation::AddDerive(op) => self.add_derive(op),
100            Operation::Transform(op) => self.transform(op),
101            Operation::RenameEnumVariant(op) => self.rename_enum_variant(op),
102            Operation::RenameFunction(op) => self.rename_function(op),
103            Operation::AddDocComment(op) => self.add_doc_comment_surgical(
104                &op.target_type,
105                &op.name,
106                &op.doc_comment,
107                &op.style,
108            ),
109            Operation::UpdateDocComment(op) => self.update_doc_comment_surgical(
110                &op.target_type,
111                &op.name,
112                &op.doc_comment,
113                &DocCommentStyle::Line, // Default to line style for updates
114            ),
115            Operation::RemoveDocComment(op) => self.remove_doc_comment_surgical(
116                &op.target_type,
117                &op.name,
118            ),
119        }
120    }
121    
122    pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
123        let mut modified_nodes = Vec::new();
124
125        // Check if this is an enum variant struct literal (contains ::)
126        let is_enum_variant = op.struct_name.contains("::");
127
128        // Check if this is literal-only mode (has literal_default but field_def has no type)
129        // This allows operating on imported structs without needing the struct definition
130        let has_type = op.field_def.contains(':');
131        let is_literal_only = op.literal_default.is_some() && !has_type;
132
133        // For enum variant literals OR literal-only operations, operate on struct literals only
134        // (enum variants: can't modify definition without full enum context)
135        // (literal-only: struct might be imported, don't need definition)
136        if is_enum_variant || is_literal_only {
137            // Parse the field_def to extract field name and value/type
138            // field_def can be:
139            // - "layer: None" (with explicit value)
140            // - "layer: Option<Layer>" (with type, needs literal_default)
141            // - "layer" (just name, needs literal_default)
142
143            let final_field_def = if let Some(literal_default) = &op.literal_default {
144                // literal_default provided - use it
145                let field_name = op.field_def.split(':')
146                    .next()
147                    .map(|s| s.trim().to_string())
148                    .context("Failed to extract field name")?;
149                format!("{}: {}", field_name, literal_default)
150            } else if op.field_def.contains(':') {
151                // field_def already contains a value (e.g., "layer: None")
152                op.field_def.clone()
153            } else {
154                anyhow::bail!(
155                    "For enum variant literals, field definition must include a value.\n\
156                     Either use: --field \"layer: None\" or --field \"layer\" --literal-default \"None\""
157                );
158            };
159
160            // Create the AddStructLiteralFieldOp
161            let literal_op = AddStructLiteralFieldOp {
162                struct_name: op.struct_name.clone(),
163                field_def: final_field_def,
164                position: op.position.clone(),
165                struct_path: None,
166            };
167
168            // Update all struct literals
169            let literal_result = self.add_struct_literal_field(&literal_op)
170                .context("Failed to update struct literals")?;
171
172            return Ok(literal_result);
173        }
174
175        // Find the struct and clone it to avoid borrowing issues
176        let item_struct = self.syntax_tree.items.iter()
177            .find_map(|item| {
178                if let Item::Struct(s) = item {
179                    if s.ident == op.struct_name {
180                        return Some(s.clone());
181                    }
182                }
183                None
184            })
185            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
186
187        // Check if the struct matches the where filter (if specified)
188        if let Some(ref where_filter) = op.where_filter {
189            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
190                // Struct doesn't match filter - skip without error
191                return Ok(ModificationResult {
192                    changed: false,
193                    modified_nodes: vec![],
194                    unmatched_qualified_paths: None,
195                });
196            }
197        }
198
199        // If literal_default is NOT provided, only modify the definition
200        if op.literal_default.is_none() {
201            // Create backup of original struct before modification
202            let backup_node = BackupNode {
203                node_type: "struct".to_string(),
204                identifier: op.struct_name.clone(),
205                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
206                location: self.span_to_location(item_struct.span()),
207            };
208
209            // Insert the field into the struct definition
210            let modified = self.insert_struct_field(&item_struct, op)
211                .context("Failed to add field to struct definition")?;
212
213            if !modified {
214                return Ok(ModificationResult {
215                    changed: false,
216                    modified_nodes: vec![],
217                    unmatched_qualified_paths: None,
218                });
219            }
220
221            return Ok(ModificationResult {
222                changed: true,
223                modified_nodes: vec![backup_node],
224                unmatched_qualified_paths: None,
225            });
226        }
227
228        // If literal_default IS provided:
229        // 1. Try to add to definition (idempotent - silently skips if field exists OR if field_def is incomplete)
230        // 2. Always update literals
231        let literal_default = op.literal_default.as_ref().unwrap();
232
233        // Check if field_def contains a type (has ':')
234        // If it doesn't, skip definition modification (literals-only mode)
235        let has_type = op.field_def.contains(':');
236
237        let mut def_modified = false;
238        if has_type {
239            // Create backup before any modifications
240            let backup_node = BackupNode {
241                node_type: "struct".to_string(),
242                identifier: op.struct_name.clone(),
243                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
244                location: self.span_to_location(item_struct.span()),
245            };
246
247            // Try to insert field into definition (idempotent - returns false if already exists)
248            def_modified = self.insert_struct_field(&item_struct, op)
249                .context("Failed to add field to struct definition")?;
250
251            if def_modified {
252                modified_nodes.push(backup_node);
253                // Re-parse the content to update syntax_tree with the struct field changes
254                self.syntax_tree = syn::parse_str(&self.content)
255                    .context("Failed to re-parse content after adding struct field")?;
256                self.line_offsets = Self::compute_line_offsets(&self.content);
257            }
258        }
259
260        // Always update literals when literal_default is provided
261        // Extract field name from field_def (e.g., "return_type: Option<Type>" -> "return_type" or just "return_type")
262        let field_name = op.field_def.split(':')
263            .next()
264            .map(|s| s.trim().to_string())
265            .context("Failed to extract field name from field definition")?;
266
267        // Create the AddStructLiteralFieldOp
268        let literal_op = AddStructLiteralFieldOp {
269            struct_name: op.struct_name.clone(),
270            field_def: format!("{}: {}", field_name, literal_default),
271            position: op.position.clone(),
272            struct_path: None,  // Path resolution not available from struct field operations
273        };
274
275        // Update all struct literals
276        let literal_result = self.add_struct_literal_field(&literal_op)
277            .context("Failed to update struct literals")?;
278        modified_nodes.extend(literal_result.modified_nodes);
279
280        Ok(ModificationResult {
281            changed: true,
282            modified_nodes,
283            unmatched_qualified_paths: None,
284        })
285    }
286    
287    fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
288        if let Fields::Named(ref fields) = item_struct.fields {
289            // Parse the new field
290            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
291            let dummy: ItemStruct = parse_str(&field_code)
292                .context("Failed to parse field definition")?;
293
294            let new_field = if let Fields::Named(ref nf) = dummy.fields {
295                nf.named.first()
296                    .context("No field found in definition")?
297                    .clone()
298            } else {
299                anyhow::bail!("Expected named field");
300            };
301
302            // Check if field already exists
303            let new_field_name = new_field.ident.as_ref()
304                .map(|i| i.to_string())
305                .context("Field must have a name")?;
306
307            if fields.named.iter().any(|f| {
308                f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
309            }) {
310                // Field already exists, skip adding
311                return Ok(false);
312            }
313            
314            // Determine insertion point
315            let insert_pos = match &op.position {
316                InsertPosition::First => {
317                    if let Some(first_field) = fields.named.first() {
318                        self.span_to_byte_offset(first_field.span().start())
319                    } else {
320                        // Empty struct, insert after the opening brace
321                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
322                        brace_pos + 1
323                    }
324                }
325                InsertPosition::Last => {
326                    if let Some(last_field) = fields.named.last() {
327                        let end = self.span_to_byte_offset(last_field.span().end());
328                        // Find the comma or end
329                        self.find_after_field_end(end)
330                    } else {
331                        // Empty struct
332                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
333                        brace_pos + 1
334                    }
335                }
336                InsertPosition::After(name) => {
337                    let field = fields.named.iter()
338                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
339                        .with_context(|| format!("Field '{}' not found", name))?;
340                    let end = self.span_to_byte_offset(field.span().end());
341                    self.find_after_field_end(end)
342                }
343                InsertPosition::Before(name) => {
344                    let field = fields.named.iter()
345                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
346                        .with_context(|| format!("Field '{}' not found", name))?;
347                    self.span_to_byte_offset(field.span().start())
348                }
349            };
350            
351            // Format the new field
352            let indent = self.get_indentation(insert_pos);
353            let field_str = Self::format_field(&new_field);
354            let insert_text = if matches!(op.position, InsertPosition::First) {
355                format!("\n{}{},", indent, field_str)
356            } else {
357                format!("\n{}{},", indent, field_str)
358            };
359
360            self.content.insert_str(insert_pos, &insert_text);
361            return Ok(true);
362        }
363        
364        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
365    }
366
367    pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
368        // Check if this is an enum variant struct literal (contains ::)
369        let is_enum_variant = op.struct_name.contains("::");
370
371        // For enum variant literals, we can't update the enum variant definition
372        // (would need full enum context), so bail with helpful message
373        if is_enum_variant {
374            anyhow::bail!(
375                "Cannot update field in enum variant definition '{}'.\n\
376                 To update fields in enum variant struct literals, use the transform command:\n\
377                 rs-hack transform --node-type struct-literal --name {} --action replace --with <new_pattern> --paths ... --apply",
378                op.struct_name, op.struct_name
379            );
380        }
381
382        // Find the struct and clone it to avoid borrowing issues
383        let item_struct = self.syntax_tree.items.iter()
384            .find_map(|item| {
385                if let Item::Struct(s) = item {
386                    if s.ident == op.struct_name {
387                        return Some(s.clone());
388                    }
389                }
390                None
391            })
392            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
393
394        // Check if the struct matches the where filter (if specified)
395        if let Some(ref where_filter) = op.where_filter {
396            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
397                // Struct doesn't match filter - skip without error
398                return Ok(ModificationResult {
399                    changed: false,
400                    modified_nodes: vec![],
401                    unmatched_qualified_paths: None,
402                });
403            }
404        }
405
406        // Create backup of original struct before modification
407        let backup_node = BackupNode {
408            node_type: "struct".to_string(),
409            identifier: op.struct_name.clone(),
410            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
411            location: self.span_to_location(item_struct.span()),
412        };
413
414        let modified = self.replace_struct_field(&item_struct, op)?;
415
416        Ok(ModificationResult {
417            changed: modified,
418            modified_nodes: if modified { vec![backup_node] } else { vec![] },
419            unmatched_qualified_paths: None,
420        })
421    }
422
423    fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
424        if let Fields::Named(ref fields) = item_struct.fields {
425            // Parse the new field definition to get the field name
426            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
427            let dummy: ItemStruct = parse_str(&field_code)
428                .context("Failed to parse field definition")?;
429
430            let new_field = if let Fields::Named(ref nf) = dummy.fields {
431                nf.named.first()
432                    .context("No field found in definition")?
433                    .clone()
434            } else {
435                anyhow::bail!("Expected named field");
436            };
437
438            // Extract the field name from the parsed field
439            let field_name = new_field.ident.as_ref()
440                .map(|i| i.to_string())
441                .context("Field must have a name")?;
442
443            // Find the existing field
444            let existing_field = fields.named.iter()
445                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
446                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
447
448            // Get the span of the existing field
449            let start = self.span_to_byte_offset(existing_field.span().start());
450            let end = self.span_to_byte_offset(existing_field.span().end());
451
452            // Format and replace the field
453            let new_field_str = Self::format_field(&new_field);
454
455            // Remove the old field and insert the new one
456            self.content.replace_range(start..end, &new_field_str);
457
458            return Ok(true);
459        }
460
461        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
462    }
463
464    pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
465        let mut modified_nodes = Vec::new();
466        let mut changed = false;
467
468        // Check if this is an enum variant struct literal (contains ::)
469        let is_enum_variant = op.struct_name.contains("::");
470
471        // For enum variant literals, automatically operate on literals only
472        // (since we can't modify the enum variant definition without the full enum context)
473        let effective_literal_only = op.literal_only || is_enum_variant;
474
475        // Step 1: Remove from struct definition (unless literal_only is true or it's an enum variant)
476        if !effective_literal_only {
477            // Find the struct and clone it to avoid borrowing issues
478            let item_struct = self.syntax_tree.items.iter()
479                .find_map(|item| {
480                    if let Item::Struct(s) = item {
481                        if s.ident == op.struct_name {
482                            return Some(s.clone());
483                        }
484                    }
485                    None
486                })
487                .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
488
489            // Check if the struct matches the where filter (if specified)
490            if let Some(ref where_filter) = op.where_filter {
491                if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
492                    // Struct doesn't match filter - skip without error
493                    return Ok(ModificationResult {
494                        changed: false,
495                        modified_nodes: vec![],
496            unmatched_qualified_paths: None,
497                    });
498                }
499            }
500
501            // Create backup of original struct before modification
502            let backup_node = BackupNode {
503                node_type: "struct".to_string(),
504                identifier: op.struct_name.clone(),
505                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
506                location: self.span_to_location(item_struct.span()),
507            };
508
509            if let Fields::Named(ref fields) = item_struct.fields {
510                // Find the field to remove
511                let field_to_remove = fields.named.iter()
512                    .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()));
513
514                // If field not found, provide helpful error with suggestions
515                if field_to_remove.is_none() {
516                    let field_names: Vec<String> = fields.named.iter()
517                        .filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
518                        .collect();
519
520                    let suggestions = Self::find_similar_fields(&op.field_name, &field_names);
521
522                    if suggestions.is_empty() {
523                        return Err(anyhow::anyhow!(
524                            "Field '{}' not found in struct '{}'\n\nAvailable fields: {}",
525                            op.field_name,
526                            op.struct_name,
527                            field_names.join(", ")
528                        ));
529                    } else {
530                        return Err(anyhow::anyhow!(
531                            "Field '{}' not found in struct '{}'\n\nDid you mean one of these?\n  - {}\n\nAll available fields: {}",
532                            op.field_name,
533                            op.struct_name,
534                            suggestions.join("\n  - "),
535                            field_names.join(", ")
536                        ));
537                    }
538                }
539
540                let field_to_remove = field_to_remove.unwrap();
541
542                // Get the span including the comma
543                let start = self.span_to_byte_offset(field_to_remove.span().start());
544                let mut end = self.span_to_byte_offset(field_to_remove.span().end());
545
546                // Find and include the comma and any trailing whitespace/newline
547                while end < self.content.len() {
548                    match self.content.as_bytes()[end] as char {
549                        ',' => {
550                            end += 1;
551                            // Also consume the newline after the comma if present
552                            if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
553                                end += 1;
554                            }
555                            break;
556                        }
557                        ' ' | '\t' => end += 1,
558                        '\n' => {
559                            end += 1;
560                            break;
561                        }
562                        _ => break,
563                    }
564                }
565
566                // Also need to remove leading whitespace/indentation on the same line
567                let mut line_start = start;
568                while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
569                    line_start -= 1;
570                }
571
572                // Check if there's only whitespace between line_start and start
573                let before_field = &self.content[line_start..start];
574                if before_field.trim().is_empty() {
575                    // Remove the whole line
576                    self.content.replace_range(line_start..end, "");
577                } else {
578                    // Just remove the field and comma
579                    self.content.replace_range(start..end, "");
580                }
581
582                modified_nodes.push(backup_node);
583                changed = true;
584
585                // Re-parse the syntax tree after surgical edit
586                self.syntax_tree = syn::parse_str(&self.content)?;
587            } else {
588                anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
589            }
590        }
591
592        // Step 2: Remove from all struct literal expressions using surgical editing
593        // Collect backups of struct literals before modification
594        let literal_backups = self.collect_struct_literal_backups(&op.struct_name, None);
595
596        // Find all field deletions (surgical approach)
597        use syn::visit::Visit;
598
599        struct FieldDeletionFinder<'a> {
600            struct_name: String,
601            field_name: String,
602            deletion_ranges: Vec<(usize, usize)>, // (start_byte, end_byte)
603            unmatched_paths: std::collections::HashMap<String, usize>, // Collect qualified paths that didn't match
604            editor: &'a RustEditor,
605        }
606
607        impl<'ast, 'a> Visit<'ast> for FieldDeletionFinder<'a> {
608            fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
609                // Check if this matches our target
610                let matches = if self.struct_name.contains("::") {
611                    if self.struct_name.starts_with("*::") {
612                        let target_name = &self.struct_name[3..];
613                        node.path.segments.last()
614                            .map(|seg| seg.ident.to_string() == target_name)
615                            .unwrap_or(false)
616                    } else {
617                        let path_str = node.path.segments.iter()
618                            .map(|seg| seg.ident.to_string())
619                            .collect::<Vec<_>>()
620                            .join("::");
621                        path_str == self.struct_name
622                    }
623                } else {
624                    // Simple name (no ::) - only match single-segment paths
625                    let matches = node.path.segments.len() == 1
626                        && node.path.segments.last()
627                            .map(|seg| seg.ident.to_string())
628                            .as_ref() == Some(&self.struct_name);
629
630                    // If doesn't match but last segment equals our struct name, track as unmatched
631                    if !matches && node.path.segments.len() > 1 {
632                        if let Some(last_seg) = node.path.segments.last() {
633                            if last_seg.ident.to_string() == self.struct_name {
634                                let qualified_path = node.path.segments.iter()
635                                    .map(|seg| seg.ident.to_string())
636                                    .collect::<Vec<_>>()
637                                    .join("::");
638                                *self.unmatched_paths.entry(qualified_path).or_insert(0) += 1;
639                            }
640                        }
641                    }
642                    matches
643                };
644
645                if matches {
646                    // Find the field to remove
647                    let field_idx = node.fields.iter().position(|fv| {
648                        if let syn::Member::Named(ident) = &fv.member {
649                            ident.to_string() == self.field_name
650                        } else {
651                            false
652                        }
653                    });
654
655                    if let Some(idx) = field_idx {
656                        let field = &node.fields[idx];
657                        let start = self.editor.span_to_byte_offset(field.span().start());
658                        let mut end = self.editor.span_to_byte_offset(field.span().end());
659
660                        // Include the trailing comma if present
661                        // Look ahead for comma and optional whitespace
662                        let content_bytes = self.editor.content.as_bytes();
663                        while end < content_bytes.len() {
664                            match content_bytes[end] {
665                                b',' => {
666                                    end += 1;
667                                    // Also consume the newline after comma if present
668                                    if end < content_bytes.len() && content_bytes[end] == b'\n' {
669                                        end += 1;
670                                    }
671                                    break;
672                                }
673                                b' ' | b'\t' => {
674                                    end += 1;
675                                }
676                                _ => break,
677                            }
678                        }
679
680                        // Also include leading whitespace/newline on the same line
681                        let mut line_start = start;
682                        while line_start > 0 {
683                            let ch = content_bytes[line_start - 1];
684                            if ch == b'\n' {
685                                break;
686                            } else if ch == b' ' || ch == b'\t' {
687                                line_start -= 1;
688                            } else {
689                                break;
690                            }
691                        }
692
693                        self.deletion_ranges.push((line_start, end));
694                    }
695                }
696
697                syn::visit::visit_expr_struct(self, node);
698            }
699        }
700
701        let mut finder = FieldDeletionFinder {
702            struct_name: op.struct_name.clone(),
703            field_name: op.field_name.clone(),
704            deletion_ranges: Vec::new(),
705            unmatched_paths: std::collections::HashMap::new(),
706            editor: self,
707        };
708
709        finder.visit_file(&self.syntax_tree);
710
711        // Prepare unmatched paths hint (only if using simple name and found unmatched paths)
712        let unmatched_hint = if !op.struct_name.contains("::") && !finder.unmatched_paths.is_empty() {
713            Some(finder.unmatched_paths)
714        } else {
715            None
716        };
717
718        if !finder.deletion_ranges.is_empty() {
719            // Sort ranges in reverse order to preserve offsets
720            let mut ranges = finder.deletion_ranges;
721            ranges.sort_by_key(|(start, _)| std::cmp::Reverse(*start));
722
723            // Perform surgical deletions
724            for (start, end) in ranges {
725                self.content.drain(start..end);
726            }
727
728            // Re-parse to update syntax_tree
729            self.syntax_tree = syn::parse_str(&self.content)
730                .context("Failed to re-parse after removing struct literal fields")?;
731            self.line_offsets = Self::compute_line_offsets(&self.content);
732
733            modified_nodes.extend(literal_backups);
734            changed = true;
735        }
736
737        Ok(ModificationResult {
738            changed,
739            modified_nodes,
740            unmatched_qualified_paths: unmatched_hint,
741        })
742    }
743
744    pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
745        // Parse the field name from field_def (e.g., "return_type: None" -> "return_type")
746        let field_name = op.field_def.split(':')
747            .next()
748            .map(|s| s.trim().to_string())
749            .context("Field definition must contain ':'")?;
750
751        // Create a path resolver if a canonical path was provided
752        let path_resolver = if let Some(struct_path) = &op.struct_path {
753            let mut resolver = PathResolver::new(struct_path)
754                .ok_or_else(|| anyhow::anyhow!("Invalid struct path: {}", struct_path))?;
755
756            // Scan the file for use statements to build the alias map
757            resolver.scan_file(&self.syntax_tree);
758            Some(resolver)
759        } else {
760            None
761        };
762
763        // Collect backups of all struct literal expressions that will be modified
764        let backup_nodes = self.collect_struct_literal_backups(&op.struct_name, path_resolver.as_ref());
765
766        // Find all struct literals and their field insertion points (surgical approach)
767        use syn::visit::Visit;
768
769        struct LiteralFieldInserter<'a> {
770            struct_name: String,
771            field_name: String,
772            path_resolver: Option<&'a PathResolver>,
773            insertion_points: Vec<(usize, usize)>, // (byte_offset, indentation_spaces)
774            unmatched_paths: std::collections::HashMap<String, usize>, // Collect qualified paths that didn't match
775            editor: &'a RustEditor,
776        }
777
778        impl<'ast, 'a> Visit<'ast> for LiteralFieldInserter<'a> {
779            fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
780                // Parse macro contents to find struct literals inside (e.g., vec![...])
781                use syn::parse::Parser;
782
783                if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
784                    .parse2(node.mac.tokens.clone())
785                {
786                    for expr in exprs.iter() {
787                        syn::visit::visit_expr(self, expr);
788                    }
789                }
790
791                syn::visit::visit_expr_macro(self, node);
792            }
793
794            fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
795                // Check if this matches our target
796                let is_match = if let Some(resolver) = &self.path_resolver {
797                    resolver.matches_target(&node.path)
798                } else {
799                    // Legacy matching logic
800                    if self.struct_name.contains("::") {
801                        if self.struct_name.starts_with("*::") {
802                            let target_name = &self.struct_name[3..];
803                            node.path.segments.last()
804                                .map(|seg| seg.ident.to_string() == target_name)
805                                .unwrap_or(false)
806                        } else {
807                            let path_str = node.path.segments.iter()
808                                .map(|seg| seg.ident.to_string())
809                                .collect::<Vec<_>>()
810                                .join("::");
811                            path_str == self.struct_name
812                        }
813                    } else {
814                        // Simple name (no ::) - only match single-segment paths
815                        let matches = node.path.segments.len() == 1
816                            && node.path.segments.last()
817                                .map(|seg| seg.ident.to_string())
818                                .as_ref() == Some(&self.struct_name);
819
820                        // If doesn't match but last segment equals our struct name, track as unmatched
821                        if !matches && node.path.segments.len() > 1 {
822                            if let Some(last_seg) = node.path.segments.last() {
823                                if last_seg.ident.to_string() == self.struct_name {
824                                    let qualified_path = node.path.segments.iter()
825                                        .map(|seg| seg.ident.to_string())
826                                        .collect::<Vec<_>>()
827                                        .join("::");
828                                    *self.unmatched_paths.entry(qualified_path).or_insert(0) += 1;
829                                }
830                            }
831                        }
832                        matches
833                    }
834                };
835
836                if is_match {
837                    // Check if field already exists
838                    let field_exists = node.fields.iter().any(|fv| {
839                        fv.member.to_token_stream().to_string() == self.field_name
840                    });
841
842                    if !field_exists {
843                        // Find insertion point after last field or after opening brace
844                        let insert_offset = if let Some(last_field) = node.fields.last() {
845                            // Insert after last field
846                            self.editor.span_to_byte_offset(last_field.span().end())
847                        } else {
848                            // No fields, insert after opening brace
849                            let brace_pos = self.editor.span_to_byte_offset(node.brace_token.span.join().start());
850                            brace_pos + 1 // After the '{'
851                        };
852
853                        // Determine indentation from the last field or struct context
854                        let indent = if let Some(last_field) = node.fields.last() {
855                            let line_start = self.editor.span_to_byte_offset(last_field.span().start());
856                            self.editor.get_indentation(line_start).len()
857                        } else {
858                            // Use struct opening indentation + 4 spaces
859                            let struct_start = self.editor.span_to_byte_offset(node.span().start());
860                            self.editor.get_indentation(struct_start).len() + 4
861                        };
862
863                        self.insertion_points.push((insert_offset, indent));
864                    }
865                }
866
867                syn::visit::visit_expr_struct(self, node);
868            }
869        }
870
871        let mut inserter = LiteralFieldInserter {
872            struct_name: op.struct_name.clone(),
873            field_name: field_name.clone(),
874            path_resolver: path_resolver.as_ref(),
875            insertion_points: Vec::new(),
876            unmatched_paths: std::collections::HashMap::new(),
877            editor: self,
878        };
879
880        inserter.visit_file(&self.syntax_tree);
881
882        // Prepare unmatched paths hint (only if using simple name and found unmatched paths)
883        let unmatched_hint = if !op.struct_name.contains("::") && !inserter.unmatched_paths.is_empty() {
884            Some(inserter.unmatched_paths)
885        } else {
886            None
887        };
888
889        if inserter.insertion_points.is_empty() {
890            return Ok(ModificationResult {
891                changed: false,
892                modified_nodes: vec![],
893                unmatched_qualified_paths: unmatched_hint,
894            });
895        }
896
897        // Sort insertion points in reverse order so we can insert from end to beginning
898        // (this way earlier offsets don't get invalidated by later insertions)
899        let mut points = inserter.insertion_points;
900        points.sort_by_key(|(offset, _)| std::cmp::Reverse(*offset));
901
902        // Perform surgical insertions
903        for (insert_offset, indent_spaces) in points {
904            let indent = " ".repeat(indent_spaces);
905            let field_str = format!(",\n{}{}", indent, op.field_def);
906            self.content.insert_str(insert_offset, &field_str);
907        }
908
909        // Re-parse to update syntax_tree
910        self.syntax_tree = syn::parse_str(&self.content)
911            .context("Failed to re-parse after adding struct literal fields")?;
912        self.line_offsets = Self::compute_line_offsets(&self.content);
913
914        Ok(ModificationResult {
915            changed: true,
916            modified_nodes: backup_nodes,
917            unmatched_qualified_paths: unmatched_hint,
918        })
919    }
920
921    /// Collect backups of all struct literal expressions for a given struct name
922    fn collect_struct_literal_backups(&self, struct_name: &str, path_resolver: Option<&PathResolver>) -> Vec<BackupNode> {
923        use syn::visit::Visit;
924        use syn::spanned::Spanned;
925
926        struct LiteralCollector<'a> {
927            struct_name: String,
928            path_resolver: Option<&'a PathResolver>,
929            backups: Vec<BackupNode>,
930            counter: usize,
931            editor: &'a RustEditor,
932        }
933
934        impl<'ast, 'a> Visit<'ast> for LiteralCollector<'a> {
935            fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
936                // Parse macro contents to find struct literals inside (e.g., vec![...])
937                use syn::parse::Parser;
938
939                if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
940                    .parse2(node.mac.tokens.clone())
941                {
942                    for expr in exprs.iter() {
943                        syn::visit::visit_expr(self, expr);
944                    }
945                }
946
947                syn::visit::visit_expr_macro(self, node);
948            }
949
950            fn visit_expr(&mut self, node: &'ast Expr) {
951                if let Expr::Struct(expr_struct) = node {
952                    let matches = if let Some(resolver) = self.path_resolver {
953                        // Use PathResolver for safe matching
954                        resolver.matches_target(&expr_struct.path)
955                    } else {
956                        // Fallback to legacy pattern matching
957                        // - "Rectangle" → only Rectangle { ... } (no :: prefix)
958                        // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
959                        // - "View::Rectangle" → exact match only View::Rectangle
960
961                        if self.struct_name.contains("::") {
962                            // Pattern contains :: - check for exact or wildcard match
963                            if self.struct_name.starts_with("*::") {
964                                // Wildcard: *::Rectangle matches any path ending with Rectangle
965                                let target_name = &self.struct_name[3..]; // Skip "*::"
966                                expr_struct.path.segments.last()
967                                    .map(|seg| seg.ident.to_string() == target_name)
968                                    .unwrap_or(false)
969                            } else {
970                                // Exact path match: View::Rectangle
971                                let path_str = expr_struct.path.segments.iter()
972                                    .map(|seg| seg.ident.to_string())
973                                    .collect::<Vec<_>>()
974                                    .join("::");
975                                path_str == self.struct_name
976                            }
977                        } else {
978                            // No :: in pattern - only match pure struct literals (no path qualifier)
979                            expr_struct.path.segments.len() == 1
980                                && expr_struct.path.segments.last()
981                                    .map(|seg| seg.ident.to_string() == self.struct_name)
982                                    .unwrap_or(false)
983                        }
984                    };
985
986                    if matches {
987                        // Extract original source text with formatting preserved
988                        let start = self.editor.span_to_byte_offset(expr_struct.span().start());
989                        let end = self.editor.span_to_byte_offset(expr_struct.span().end());
990                        let original_source = &self.editor.content[start..end];
991
992                        self.backups.push(BackupNode {
993                            node_type: "struct-literal".to_string(),
994                            identifier: format!("{}#{}", self.struct_name, self.counter),
995                            original_content: original_source.to_string(),
996                            location: NodeLocation {
997                                line: 0, // We don't have precise location info in visitor
998                                column: 0,
999                                end_line: 0,
1000                                end_column: 0,
1001                            },
1002                        });
1003                        self.counter += 1;
1004                    }
1005                }
1006                syn::visit::visit_expr(self, node);
1007            }
1008        }
1009
1010        let mut collector = LiteralCollector {
1011            struct_name: struct_name.to_string(),
1012            path_resolver,
1013            backups: Vec::new(),
1014            counter: 0,
1015            editor: self,
1016        };
1017
1018        collector.visit_file(&self.syntax_tree);
1019        collector.backups
1020    }
1021
1022    pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
1023        // Find the enum and clone it to avoid borrowing issues
1024        let item_enum = self.syntax_tree.items.iter()
1025            .find_map(|item| {
1026                if let Item::Enum(e) = item {
1027                    if e.ident == op.enum_name {
1028                        return Some(e.clone());
1029                    }
1030                }
1031                None
1032            })
1033            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1034
1035        // Check if the enum matches the where filter (if specified)
1036        if let Some(ref where_filter) = op.where_filter {
1037            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1038                // Enum doesn't match filter - skip without error
1039                return Ok(ModificationResult {
1040                    changed: false,
1041                    modified_nodes: vec![],
1042                    unmatched_qualified_paths: None,
1043                });
1044            }
1045        }
1046
1047        // Create backup of original enum before modification
1048        let backup_node = BackupNode {
1049            node_type: "enum".to_string(),
1050            identifier: op.enum_name.clone(),
1051            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1052            location: self.span_to_location(item_enum.span()),
1053        };
1054
1055        let modified = self.insert_enum_variant(&item_enum, op)?;
1056
1057        Ok(ModificationResult {
1058            changed: modified,
1059            modified_nodes: if modified { vec![backup_node] } else { vec![] },
1060            unmatched_qualified_paths: None,
1061        })
1062    }
1063    
1064    fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
1065        // Parse the new variant
1066        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
1067        let dummy: ItemEnum = parse_str(&variant_code)
1068            .context("Failed to parse variant definition")?;
1069
1070        let new_variant = dummy.variants.first()
1071            .context("No variant found in definition")?
1072            .clone();
1073
1074        // Check if variant already exists
1075        let variant_name = new_variant.ident.to_string();
1076        if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
1077            // Variant already exists, skip adding
1078            return Ok(false);
1079        }
1080
1081        // Determine insertion point
1082        let insert_pos = match &op.position {
1083            InsertPosition::First => {
1084                if let Some(first_var) = item_enum.variants.first() {
1085                    self.span_to_byte_offset(first_var.span().start())
1086                } else {
1087                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
1088                    brace_pos + 1
1089                }
1090            }
1091            InsertPosition::Last => {
1092                if let Some(last_var) = item_enum.variants.last() {
1093                    let end = self.span_to_byte_offset(last_var.span().end());
1094                    self.find_after_field_end(end)
1095                } else {
1096                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
1097                    brace_pos + 1
1098                }
1099            }
1100            InsertPosition::After(name) => {
1101                let variant = item_enum.variants.iter()
1102                    .find(|v| v.ident.to_string() == *name)
1103                    .with_context(|| format!("Variant '{}' not found", name))?;
1104                let end = self.span_to_byte_offset(variant.span().end());
1105                self.find_after_field_end(end)
1106            }
1107            InsertPosition::Before(name) => {
1108                let variant = item_enum.variants.iter()
1109                    .find(|v| v.ident.to_string() == *name)
1110                    .with_context(|| format!("Variant '{}' not found", name))?;
1111                self.span_to_byte_offset(variant.span().start())
1112            }
1113        };
1114        
1115        let indent = self.get_indentation(insert_pos);
1116        let variant_str = new_variant.to_token_stream().to_string();
1117        let insert_text = format!("\n{}{},", indent, variant_str);
1118        
1119        self.content.insert_str(insert_pos, &insert_text);
1120        Ok(true)
1121    }
1122
1123    fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
1124        // Find the enum and clone it
1125        let item_enum = self.syntax_tree.items.iter()
1126            .find_map(|item| {
1127                if let Item::Enum(e) = item {
1128                    if e.ident == op.enum_name {
1129                        return Some(e.clone());
1130                    }
1131                }
1132                None
1133            })
1134            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1135
1136        // Check if the enum matches the where filter (if specified)
1137        if let Some(ref where_filter) = op.where_filter {
1138            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1139                // Enum doesn't match filter - skip without error
1140                return Ok(ModificationResult {
1141                    changed: false,
1142                    modified_nodes: vec![],
1143                    unmatched_qualified_paths: None,
1144                });
1145            }
1146        }
1147
1148        // Create backup of original enum before modification
1149        let backup_node = BackupNode {
1150            node_type: "enum".to_string(),
1151            identifier: op.enum_name.clone(),
1152            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1153            location: self.span_to_location(item_enum.span()),
1154        };
1155
1156        // Parse the new variant to get its name
1157        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
1158        let dummy: ItemEnum = parse_str(&variant_code)
1159            .context("Failed to parse variant definition")?;
1160
1161        let new_variant = dummy.variants.first()
1162            .context("No variant found in definition")?
1163            .clone();
1164
1165        let variant_name = new_variant.ident.to_string();
1166
1167        // Find the existing variant
1168        let existing_variant = item_enum.variants.iter()
1169            .find(|v| v.ident.to_string() == variant_name)
1170            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
1171
1172        // Get the span
1173        let start = self.span_to_byte_offset(existing_variant.span().start());
1174        let end = self.span_to_byte_offset(existing_variant.span().end());
1175
1176        // Format and replace
1177        let variant_str = new_variant.to_token_stream().to_string();
1178        self.content.replace_range(start..end, &variant_str);
1179
1180        Ok(ModificationResult {
1181            changed: true,
1182            modified_nodes: vec![backup_node],
1183            unmatched_qualified_paths: None,
1184        })
1185    }
1186
1187    pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
1188        // Find the enum
1189        let item_enum = self.syntax_tree.items.iter()
1190            .find_map(|item| {
1191                if let Item::Enum(e) = item {
1192                    if e.ident == op.enum_name {
1193                        return Some(e.clone());
1194                    }
1195                }
1196                None
1197            })
1198            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
1199
1200        // Check if the enum matches the where filter (if specified)
1201        if let Some(ref where_filter) = op.where_filter {
1202            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
1203                // Enum doesn't match filter - skip without error
1204                return Ok(ModificationResult {
1205                    changed: false,
1206                    modified_nodes: vec![],
1207                    unmatched_qualified_paths: None,
1208                });
1209            }
1210        }
1211
1212        // Create backup of original enum before modification
1213        let backup_node = BackupNode {
1214            node_type: "enum".to_string(),
1215            identifier: op.enum_name.clone(),
1216            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
1217            location: self.span_to_location(item_enum.span()),
1218        };
1219
1220        // Find the variant to remove
1221        let variant_to_remove = item_enum.variants.iter()
1222            .find(|v| v.ident.to_string() == op.variant_name)
1223            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
1224
1225        // Get the span including comma
1226        let start = self.span_to_byte_offset(variant_to_remove.span().start());
1227        let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
1228
1229        // Find and include the comma and trailing whitespace
1230        while end < self.content.len() {
1231            match self.content.as_bytes()[end] as char {
1232                ',' => {
1233                    end += 1;
1234                    if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
1235                        end += 1;
1236                    }
1237                    break;
1238                }
1239                ' ' | '\t' => end += 1,
1240                '\n' => {
1241                    end += 1;
1242                    break;
1243                }
1244                _ => break,
1245            }
1246        }
1247
1248        // Remove leading whitespace on the line
1249        let mut line_start = start;
1250        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1251            line_start -= 1;
1252        }
1253
1254        let before_variant = &self.content[line_start..start];
1255        if before_variant.trim().is_empty() {
1256            self.content.replace_range(line_start..end, "");
1257        } else {
1258            self.content.replace_range(start..end, "");
1259        }
1260
1261        Ok(ModificationResult {
1262            changed: true,
1263            modified_nodes: vec![backup_node],
1264            unmatched_qualified_paths: None,
1265        })
1266    }
1267
1268    pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1269        if op.auto_detect {
1270            // Auto-detect mode: find all missing enum variants
1271            self.add_missing_match_arms(op)
1272        } else {
1273            // Normal mode: add a single match arm
1274            self.add_single_match_arm(op)
1275        }
1276    }
1277
1278    fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1279        // Parse the pattern and body by creating a dummy match expression
1280        let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
1281        let expr: syn::Expr = parse_str(&dummy_match)
1282            .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
1283
1284        // Extract the arm from the dummy match
1285        let arm = if let syn::Expr::Match(match_expr) = expr {
1286            match_expr.arms.into_iter().next()
1287                .context("Failed to extract arm from dummy match")?
1288        } else {
1289            anyhow::bail!("Expected match expression");
1290        };
1291
1292        // Collect backup of function before modification
1293        let backup_node = if let Some(ref fn_name) = op.function_name {
1294            self.get_function_backup(fn_name)?
1295        } else {
1296            // If no function specified, we'll backup all modified functions later
1297            // For now, create a generic backup
1298            BackupNode {
1299                node_type: "Unknown".to_string(),
1300                identifier: "match_expression".to_string(),
1301                original_content: String::new(),
1302                location: NodeLocation {
1303                    line: 0,
1304                    column: 0,
1305                    end_line: 0,
1306                    end_column: 0,
1307                },
1308            }
1309        };
1310
1311        // Find and modify match expressions
1312        let mut visitor = MatchArmAdder {
1313            target_function: op.function_name.clone(),
1314            arm_to_add: arm,
1315            modified: false,
1316            current_function: None,
1317            modified_function: None,
1318        };
1319
1320        visitor.visit_file_mut(&mut self.syntax_tree);
1321
1322        if visitor.modified {
1323            // Replace just the modified function
1324            self.replace_modified_functions(&visitor.modified_function)?;
1325            Ok(ModificationResult {
1326                changed: true,
1327                modified_nodes: vec![backup_node],
1328            unmatched_qualified_paths: None,
1329            })
1330        } else {
1331            Ok(ModificationResult {
1332                changed: false,
1333                modified_nodes: vec![],
1334            unmatched_qualified_paths: None,
1335            })
1336        }
1337    }
1338
1339    /// Format a single item to string using prettyplease
1340    fn unparse_item(&self, item: &Item) -> String {
1341        let temp_file = syn::File {
1342            shebang: None,
1343            attrs: Vec::new(),
1344            items: vec![item.clone()],
1345        };
1346        prettyplease::unparse(&temp_file).trim().to_string()
1347    }
1348
1349    /// Isolated prettyplease: Reformat only a specific item, preserving the rest of the file
1350    /// This finds an item in the original syntax tree, formats it with prettyplease, and surgically replaces it
1351    fn reformat_item_isolated<F>(&mut self, predicate: F) -> Result<bool>
1352    where
1353        F: Fn(&Item) -> bool,
1354    {
1355        // Find the item index in the ORIGINAL syntax tree (before mutations)
1356        let original_syntax_tree: syn::File = syn::parse_str(&self.content)
1357            .context("Failed to parse original content")?;
1358
1359        let (item_index, original_item) = original_syntax_tree.items.iter()
1360            .enumerate()
1361            .find(|(_, item)| predicate(item))
1362            .ok_or_else(|| anyhow::anyhow!("Item not found"))?;
1363
1364        // Get the byte range of the original item
1365        let start = self.span_to_byte_offset(original_item.span().start());
1366        let end = self.span_to_byte_offset(original_item.span().end());
1367
1368        // Get the modified item from the current syntax tree
1369        if item_index >= self.syntax_tree.items.len() {
1370            anyhow::bail!("Item index out of bounds after modification");
1371        }
1372        let modified_item = &self.syntax_tree.items[item_index];
1373
1374        // Format just this item with prettyplease
1375        let formatted_item = self.unparse_item(modified_item);
1376
1377        // Surgically replace the old item with the formatted version
1378        self.content.replace_range(start..end, &formatted_item);
1379
1380        // Re-parse to update syntax_tree
1381        self.syntax_tree = syn::parse_str(&self.content)
1382            .context("Failed to re-parse after isolated prettyplease")?;
1383        self.line_offsets = Self::compute_line_offsets(&self.content);
1384
1385        Ok(true)
1386    }
1387
1388    /// Get backup of a function before modification
1389    fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
1390        for item in &self.syntax_tree.items {
1391            if let Item::Fn(f) = item {
1392                if f.sig.ident == fn_name {
1393                    return Ok(BackupNode {
1394                        node_type: "function".to_string(),
1395                        identifier: fn_name.to_string(),
1396                        original_content: self.unparse_item(&Item::Fn(f.clone())),
1397                        location: self.span_to_location(f.span()),
1398                    });
1399                }
1400            }
1401        }
1402        anyhow::bail!("Function '{}' not found", fn_name)
1403    }
1404
1405    fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
1406        // Get the enum name
1407        let enum_name = op.enum_name.as_ref()
1408            .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
1409
1410        // Find all enum variants
1411        let enum_variants = self.find_enum_variants(enum_name)?;
1412
1413        if enum_variants.is_empty() {
1414            anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
1415        }
1416
1417        // Find existing match arms
1418        let existing_patterns = self.find_existing_match_patterns(&op.function_name);
1419
1420        // Determine missing variants
1421        let mut missing_variants = Vec::new();
1422        for variant in &enum_variants {
1423            let pattern = format!("{}::{}", enum_name, variant);
1424            let pattern_normalized = pattern.replace(" ", "");
1425
1426            let exists = existing_patterns.iter().any(|p| {
1427                p.replace(" ", "") == pattern_normalized
1428            });
1429
1430            if !exists {
1431                missing_variants.push(variant.clone());
1432            }
1433        }
1434
1435        if missing_variants.is_empty() {
1436            println!("All enum variants already covered in match expressions");
1437            return Ok(ModificationResult {
1438                changed: false,
1439                modified_nodes: vec![],
1440                unmatched_qualified_paths: None,
1441            });
1442        }
1443
1444        // Get backup of function before modification
1445        let backup_node = if let Some(ref fn_name) = op.function_name {
1446            self.get_function_backup(fn_name)?
1447        } else {
1448            BackupNode {
1449                node_type: "Unknown".to_string(),
1450                identifier: "match_expression".to_string(),
1451                original_content: String::new(),
1452                location: NodeLocation {
1453                    line: 0,
1454                    column: 0,
1455                    end_line: 0,
1456                    end_column: 0,
1457                },
1458            }
1459        };
1460
1461        // Add ALL missing match arms in one pass using a visitor
1462        let mut arms_to_add = Vec::new();
1463        for variant in &missing_variants {
1464            let pattern = format!("{}::{}", enum_name, variant);
1465            let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
1466            let expr: syn::Expr = parse_str(&dummy_match)
1467                .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
1468
1469            if let syn::Expr::Match(match_expr) = expr {
1470                if let Some(arm) = match_expr.arms.into_iter().next() {
1471                    arms_to_add.push((pattern.clone(), arm));
1472                }
1473            }
1474        }
1475
1476        // Find and modify match expressions with all arms at once
1477        let mut visitor = MultiMatchArmAdder {
1478            target_function: op.function_name.clone(),
1479            arms_to_add,
1480            modified: false,
1481            current_function: None,
1482            modified_function: None,
1483        };
1484
1485        visitor.visit_file_mut(&mut self.syntax_tree);
1486
1487        if visitor.modified {
1488            // Print what was added
1489            for variant in &missing_variants {
1490                println!("Added match arm for: {}::{}", enum_name, variant);
1491            }
1492
1493            // Replace just the modified function
1494            self.replace_modified_functions(&visitor.modified_function)?;
1495            Ok(ModificationResult {
1496                changed: true,
1497                modified_nodes: vec![backup_node],
1498            unmatched_qualified_paths: None,
1499            })
1500        } else {
1501            Ok(ModificationResult {
1502                changed: false,
1503                modified_nodes: vec![],
1504            unmatched_qualified_paths: None,
1505            })
1506        }
1507    }
1508
1509    fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
1510        // Find the enum in the syntax tree
1511        for item in &self.syntax_tree.items {
1512            if let Item::Enum(e) = item {
1513                if e.ident == enum_name {
1514                    let variants: Vec<String> = e.variants.iter()
1515                        .map(|v| v.ident.to_string())
1516                        .collect();
1517                    return Ok(variants);
1518                }
1519            }
1520        }
1521
1522        Ok(Vec::new())
1523    }
1524
1525    fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1526        use syn::visit::Visit;
1527
1528        struct PatternCollector {
1529            target_function: Option<String>,
1530            current_function: Option<String>,
1531            patterns: Vec<String>,
1532        }
1533
1534        impl<'ast> Visit<'ast> for PatternCollector {
1535            fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1536                let prev_fn = self.current_function.clone();
1537                self.current_function = Some(node.sig.ident.to_string());
1538                syn::visit::visit_item_fn(self, node);
1539                self.current_function = prev_fn;
1540            }
1541
1542            fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1543                // Check if we're in the right function (if specified)
1544                if let Some(ref target) = self.target_function {
1545                    if self.current_function.as_ref() != Some(target) {
1546                        syn::visit::visit_expr_match(self, node);
1547                        return;
1548                    }
1549                }
1550
1551                // Collect all patterns
1552                for arm in &node.arms {
1553                    self.patterns.push(arm.pat.to_token_stream().to_string());
1554                }
1555
1556                syn::visit::visit_expr_match(self, node);
1557            }
1558        }
1559
1560        let mut collector = PatternCollector {
1561            target_function: function_name.clone(),
1562            current_function: None,
1563            patterns: Vec::new(),
1564        };
1565
1566        collector.visit_file(&self.syntax_tree);
1567        collector.patterns
1568    }
1569
1570    pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1571        // Get backup of function before modification
1572        let backup_node = if let Some(ref fn_name) = op.function_name {
1573            self.get_function_backup(fn_name)?
1574        } else {
1575            BackupNode {
1576                node_type: "Unknown".to_string(),
1577                identifier: "match_expression".to_string(),
1578                original_content: String::new(),
1579                location: NodeLocation {
1580                    line: 0,
1581                    column: 0,
1582                    end_line: 0,
1583                    end_column: 0,
1584                },
1585            }
1586        };
1587
1588        // Parse the new body
1589        let new_body: syn::Expr = parse_str(&op.new_body)
1590            .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1591
1592        // Find and modify match expressions
1593        let mut visitor = MatchArmUpdater {
1594            target_function: op.function_name.clone(),
1595            pattern_to_match: op.pattern.clone(),
1596            new_body,
1597            modified: false,
1598            current_function: None,
1599            modified_function: None,
1600        };
1601
1602        visitor.visit_file_mut(&mut self.syntax_tree);
1603
1604        if visitor.modified {
1605            // Replace just the modified function
1606            self.replace_modified_functions(&visitor.modified_function)?;
1607            Ok(ModificationResult {
1608                changed: true,
1609                modified_nodes: vec![backup_node],
1610            unmatched_qualified_paths: None,
1611            })
1612        } else {
1613            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1614        }
1615    }
1616
1617    pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1618        // Get backup of function before modification
1619        let backup_node = if let Some(ref fn_name) = op.function_name {
1620            self.get_function_backup(fn_name)?
1621        } else {
1622            BackupNode {
1623                node_type: "Unknown".to_string(),
1624                identifier: "match_expression".to_string(),
1625                original_content: String::new(),
1626                location: NodeLocation {
1627                    line: 0,
1628                    column: 0,
1629                    end_line: 0,
1630                    end_column: 0,
1631                },
1632            }
1633        };
1634
1635        // Find and modify match expressions
1636        let mut visitor = MatchArmRemover {
1637            target_function: op.function_name.clone(),
1638            pattern_to_remove: op.pattern.clone(),
1639            modified: false,
1640            current_function: None,
1641            modified_function: None,
1642        };
1643
1644        visitor.visit_file_mut(&mut self.syntax_tree);
1645
1646        if visitor.modified {
1647            // Replace just the modified function
1648            self.replace_modified_functions(&visitor.modified_function)?;
1649            Ok(ModificationResult {
1650                changed: true,
1651                modified_nodes: vec![backup_node],
1652            unmatched_qualified_paths: None,
1653            })
1654        } else {
1655            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1656        }
1657    }
1658
1659    pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1660        // Parse the method definition
1661        let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1662        let dummy: syn::ItemImpl = parse_str(&method_code)
1663            .context("Failed to parse method definition")?;
1664
1665        let new_method = dummy.items.first()
1666            .context("No method found in definition")?
1667            .clone();
1668
1669        // Get the method name for idempotency check
1670        let method_name = match &new_method {
1671            syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1672            _ => anyhow::bail!("Only method definitions are supported"),
1673        };
1674
1675        // Find the impl block
1676        let impl_index = self.syntax_tree.items.iter().position(|item| {
1677            if let Item::Impl(impl_block) = item {
1678                // Check if this is the right impl block
1679                if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1680                    if let Some(segment) = type_path.path.segments.last() {
1681                        return segment.ident == op.target;
1682                    }
1683                }
1684            }
1685            false
1686        }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1687
1688        // Check if method already exists (idempotent)
1689        let impl_block = match &self.syntax_tree.items[impl_index] {
1690            Item::Impl(i) => i,
1691            _ => unreachable!(),
1692        };
1693
1694        let method_exists = impl_block.items.iter().any(|item| {
1695            if let syn::ImplItem::Fn(f) = item {
1696                f.sig.ident == method_name
1697            } else {
1698                false
1699            }
1700        });
1701
1702        if method_exists {
1703            return Ok(ModificationResult {
1704                changed: false,
1705                modified_nodes: vec![],
1706                unmatched_qualified_paths: None,
1707            });
1708        }
1709
1710        // Create backup of original impl block before modification
1711        let backup_node = BackupNode {
1712            node_type: "ItemImpl".to_string(),
1713            identifier: op.target.clone(),
1714            original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1715            location: self.span_to_location(impl_block.span()),
1716        };
1717
1718        // Get the span before modification
1719        let impl_span = impl_block.span();
1720
1721        // Add the method to the impl block
1722        match &mut self.syntax_tree.items[impl_index] {
1723            Item::Impl(impl_block) => {
1724                // Add based on position
1725                match &op.position {
1726                    InsertPosition::First => {
1727                        impl_block.items.insert(0, new_method);
1728                    }
1729                    InsertPosition::Last => {
1730                        impl_block.items.push(new_method);
1731                    }
1732                    InsertPosition::After(name) => {
1733                        let pos = impl_block.items.iter().position(|item| {
1734                            if let syn::ImplItem::Fn(f) = item {
1735                                f.sig.ident == name
1736                            } else {
1737                                false
1738                            }
1739                        }).with_context(|| format!("Method '{}' not found", name))?;
1740                        impl_block.items.insert(pos + 1, new_method);
1741                    }
1742                    InsertPosition::Before(name) => {
1743                        let pos = impl_block.items.iter().position(|item| {
1744                            if let syn::ImplItem::Fn(f) = item {
1745                                f.sig.ident == name
1746                            } else {
1747                                false
1748                            }
1749                        }).with_context(|| format!("Method '{}' not found", name))?;
1750                        impl_block.items.insert(pos, new_method);
1751                    }
1752                }
1753            }
1754            _ => unreachable!(),
1755        }
1756
1757        // Use prettyplease to format just this impl block
1758        self.replace_formatted_item(impl_index, impl_span)?;
1759
1760        Ok(ModificationResult {
1761            changed: true,
1762            modified_nodes: vec![backup_node],
1763            unmatched_qualified_paths: None,
1764        })
1765    }
1766
1767    pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1768        // Parse the use statement
1769        let use_code = format!("use {};", op.use_path);
1770        let use_item: syn::ItemUse = parse_str(&use_code)
1771            .context("Failed to parse use statement")?;
1772
1773        // Check if this use statement already exists (idempotent)
1774        let use_exists = self.syntax_tree.items.iter().any(|item| {
1775            if let Item::Use(existing_use) = item {
1776                // Compare the use trees
1777                existing_use.tree.to_token_stream().to_string() ==
1778                    use_item.tree.to_token_stream().to_string()
1779            } else {
1780                false
1781            }
1782        });
1783
1784        if use_exists {
1785            return Ok(ModificationResult {
1786                changed: false,
1787                modified_nodes: vec![],
1788                unmatched_qualified_paths: None,
1789            });
1790        }
1791
1792        // Create a simple backup for use statements (track by line position)
1793        let backup_node = BackupNode {
1794            node_type: "ItemUse".to_string(),
1795            identifier: op.use_path.clone(),
1796            original_content: format!("use {};", op.use_path),
1797            location: NodeLocation {
1798                line: 0,
1799                column: 0,
1800                end_line: 0,
1801                end_column: 0,
1802            },
1803        };
1804
1805        // Find the position to insert the use statement
1806        let insert_index = match &op.position {
1807            InsertPosition::First => 0,
1808            InsertPosition::Last => {
1809                // Find the last use statement
1810                self.syntax_tree.items.iter()
1811                    .rposition(|item| matches!(item, Item::Use(_)))
1812                    .map(|i| i + 1)
1813                    .unwrap_or(0)
1814            }
1815            InsertPosition::After(path) => {
1816                // Find the use statement matching the path
1817                let pos = self.syntax_tree.items.iter().position(|item| {
1818                    if let Item::Use(u) = item {
1819                        u.tree.to_token_stream().to_string().contains(path)
1820                    } else {
1821                        false
1822                    }
1823                }).with_context(|| format!("Use statement for '{}' not found", path))?;
1824                pos + 1
1825            }
1826            InsertPosition::Before(path) => {
1827                // Find the use statement matching the path
1828                self.syntax_tree.items.iter().position(|item| {
1829                    if let Item::Use(u) = item {
1830                        u.tree.to_token_stream().to_string().contains(path)
1831                    } else {
1832                        false
1833                    }
1834                }).with_context(|| format!("Use statement for '{}' not found", path))?
1835            }
1836        };
1837
1838        // Insert the use statement into the AST
1839        self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1840
1841        // Find the byte position in the source where we need to insert
1842        // We want to insert at the beginning of a line
1843        let insert_line_pos = if insert_index == 0 {
1844            // Insert at very beginning
1845            0
1846        } else {
1847            // Insert after the previous item
1848            let prev_item = &self.syntax_tree.items[insert_index - 1];
1849            let span = prev_item.span();
1850            let end_pos = self.span_to_byte_offset(span.end());
1851
1852            // Find the end of this line (where the newline is)
1853            let mut line_end = end_pos;
1854            while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1855                line_end += 1;
1856            }
1857            // Move past the newline to the start of the next line
1858            if line_end < self.content.len() {
1859                line_end + 1
1860            } else {
1861                // At end of file, add a newline first
1862                self.content.push('\n');
1863                self.content.len()
1864            }
1865        };
1866
1867        // Format the use statement
1868        let use_str = format!("use {};\n", op.use_path);
1869
1870        // Insert the use statement
1871        self.content.insert_str(insert_line_pos, &use_str);
1872
1873        Ok(ModificationResult {
1874            changed: true,
1875            modified_nodes: vec![backup_node],
1876            unmatched_qualified_paths: None,
1877        })
1878    }
1879
1880    pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1881        // Find the target item (struct or enum)
1882        let item_index = self.syntax_tree.items.iter().position(|item| {
1883            match (&op.target_type as &str, item) {
1884                ("struct", Item::Struct(s)) => s.ident == op.target_name,
1885                ("enum", Item::Enum(e)) => e.ident == op.target_name,
1886                _ => false,
1887            }
1888        }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1889
1890        // Get the item and check for existing derives
1891        let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1892            Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1893            Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1894            _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1895        };
1896
1897        // Check if the item matches the where filter (if specified)
1898        if let Some(ref where_filter) = op.where_filter {
1899            if !self.matches_where_filter(item_attrs, where_filter)? {
1900                // Item doesn't match filter - skip without error
1901                return Ok(ModificationResult {
1902                    changed: false,
1903                    modified_nodes: vec![],
1904                    unmatched_qualified_paths: None,
1905                });
1906            }
1907        }
1908
1909        // Create backup of original item before modification
1910        let backup_node = BackupNode {
1911            node_type: if op.target_type == "struct" { "struct" } else { "enum" }.to_string(),
1912            identifier: op.target_name.clone(),
1913            original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1914            location: self.span_to_location(item_span),
1915        };
1916
1917        // Filter out derives that already exist (idempotent)
1918        let new_derives: Vec<String> = op.derives.iter()
1919            .filter(|d| !existing_derives.contains(&d.to_string()))
1920            .cloned()
1921            .collect();
1922
1923        if new_derives.is_empty() {
1924            // All derives already exist
1925            return Ok(ModificationResult {
1926                changed: false,
1927                modified_nodes: vec![],
1928                unmatched_qualified_paths: None,
1929            });
1930        }
1931
1932        // Combine existing and new derives
1933        let mut all_derives = existing_derives;
1934        all_derives.extend(new_derives);
1935
1936        // Convert to string refs for the update function
1937        let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1938
1939        // Update the AST item's attributes
1940        match &mut self.syntax_tree.items[item_index] {
1941            Item::Struct(s) => {
1942                Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1943            }
1944            Item::Enum(e) => {
1945                Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1946            }
1947            _ => unreachable!(),
1948        }
1949
1950        // Use prettyplease to format just this item
1951        self.replace_formatted_item(item_index, item_span)?;
1952
1953        Ok(ModificationResult {
1954            changed: true,
1955            modified_nodes: vec![backup_node],
1956            unmatched_qualified_paths: None,
1957        })
1958    }
1959
1960    /// Replace an item in the content with a formatted version
1961    fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1962        // Get the item start and end positions from the original source
1963        let item_start_pos = self.span_to_byte_offset(original_span.start());
1964        let item_end_pos = self.span_to_byte_offset(original_span.end());
1965
1966        // Find the actual start (including attributes)
1967        let mut actual_start = item_start_pos;
1968
1969        // Search backwards for attributes
1970        let mut temp_pos = item_start_pos;
1971        while temp_pos > 0 {
1972            // Move to previous line
1973            temp_pos = temp_pos.saturating_sub(1);
1974            let mut line_start = temp_pos;
1975            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1976                line_start -= 1;
1977            }
1978
1979            let line = if temp_pos < self.content.len() {
1980                &self.content[line_start..temp_pos + 1]
1981            } else {
1982                &self.content[line_start..]
1983            };
1984            let trimmed = line.trim();
1985
1986            if trimmed.starts_with("#[") {
1987                actual_start = line_start;
1988                temp_pos = line_start;
1989            } else if trimmed.is_empty() {
1990                temp_pos = line_start;
1991            } else {
1992                break;
1993            }
1994
1995            if line_start == 0 {
1996                break;
1997            }
1998        }
1999
2000        // Create a temporary file with just this item for pretty formatting
2001        let item_clone = self.syntax_tree.items[item_index].clone();
2002        let temp_file = syn::File {
2003            shebang: None,
2004            attrs: Vec::new(),
2005            items: vec![item_clone],
2006        };
2007
2008        // Format the item using prettyplease
2009        let formatted = prettyplease::unparse(&temp_file);
2010        let formatted = formatted.trim();
2011
2012        // Replace in content
2013        self.content.replace_range(actual_start..item_end_pos, formatted);
2014
2015        Ok(())
2016    }
2017
2018    /// Extract existing derive traits from attributes - returns owned Strings
2019    fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
2020        for attr in attrs {
2021            if attr.path().is_ident("derive") {
2022                if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
2023                    let tokens_str = meta_list.tokens.to_string();
2024                    return tokens_str
2025                        .split(',')
2026                        .map(|s| s.trim().to_string())
2027                        .collect();
2028                }
2029            }
2030        }
2031        Vec::new()
2032    }
2033
2034    /// Check if an item matches the where filter criteria
2035    /// Supports filters like:
2036    /// - "derives_trait:Clone" - matches if item derives Clone
2037    /// - "derives_trait:Clone,Debug" - matches if item derives Clone OR Debug
2038    fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
2039        // Parse the filter: "derives_trait:Clone,Debug"
2040        if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
2041            let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
2042            let existing_derives = Self::extract_derives(attrs);
2043
2044            // Check if ANY of the required traits are present
2045            for required_trait in required_traits {
2046                if existing_derives.iter().any(|d| d == required_trait) {
2047                    return Ok(true);
2048                }
2049            }
2050            return Ok(false);
2051        }
2052
2053        // Unknown filter type - default to match (don't break existing behavior)
2054        Ok(true)
2055    }
2056
2057    /// Update or create derive attribute in the attribute list
2058    fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
2059        let derive_str = derives.join(", ");
2060
2061        // Parse a dummy struct with the derive to extract the attribute
2062        let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
2063        let parsed: syn::ItemStruct = parse_str(&dummy)
2064            .context("Failed to parse derive attribute")?;
2065
2066        let new_attr = parsed.attrs.into_iter()
2067            .find(|a| a.path().is_ident("derive"))
2068            .context("Failed to extract derive attribute")?;
2069
2070        // Find existing derive attribute and replace it
2071        if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
2072            attrs[pos] = new_attr;
2073        } else {
2074            // Add new derive attribute at the beginning
2075            attrs.insert(0, new_attr);
2076        }
2077
2078        Ok(())
2079    }
2080
2081    /// Replace the modified function(s) in the content with formatted versions
2082    fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
2083        // If no specific function was targeted, use isolated prettyplease for all modified functions
2084        if modified_function.is_none() {
2085            // This case shouldn't happen in practice, but if it does, fall back to whole-file format
2086            // TODO: Track which functions were modified and format only those
2087            self.content = prettyplease::unparse(&self.syntax_tree);
2088            return Ok(());
2089        }
2090
2091        // Parse the ORIGINAL content to get the correct spans
2092        let original_syntax_tree: File = syn::parse_str(&self.content)
2093            .context("Failed to re-parse original content")?;
2094
2095        let function_name = modified_function.as_ref().unwrap();
2096
2097        // Find the function in the ORIGINAL syntax tree to get correct byte positions
2098        let original_fn = original_syntax_tree.items.iter()
2099            .find_map(|item| {
2100                if let Item::Fn(f) = item {
2101                    if f.sig.ident == function_name {
2102                        return Some(f.clone());
2103                    }
2104                }
2105                None
2106            })
2107            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
2108
2109        // Get the span of the original function (these are the correct byte positions)
2110        let start = self.span_to_byte_offset(original_fn.span().start());
2111        let end = self.span_to_byte_offset(original_fn.span().end());
2112
2113        // Find the MODIFIED function in the modified syntax tree
2114        let modified_fn = self.syntax_tree.items.iter()
2115            .find_map(|item| {
2116                if let Item::Fn(f) = item {
2117                    if f.sig.ident == function_name {
2118                        return Some(f.clone());
2119                    }
2120                }
2121                None
2122            })
2123            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
2124
2125        // Format just the modified function using prettyplease
2126        let dummy_file = syn::File {
2127            shebang: None,
2128            attrs: Vec::new(),
2129            items: vec![Item::Fn(modified_fn)],
2130        };
2131
2132        let formatted_fn = prettyplease::unparse(&dummy_file);
2133
2134        // Extract just the function (remove any extra newlines at start/end)
2135        let formatted_fn = formatted_fn.trim();
2136
2137        // Replace the function in the original content using original spans
2138        self.content.replace_range(start..end, formatted_fn);
2139
2140        Ok(())
2141    }
2142
2143    pub fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
2144        let line_idx = pos.line.saturating_sub(1);
2145        if line_idx < self.line_offsets.len() {
2146            self.line_offsets[line_idx] + pos.column
2147        } else {
2148            self.content.len()
2149        }
2150    }
2151    
2152    fn find_after_field_end(&self, pos: usize) -> usize {
2153        // Look for comma or newline after the field
2154        let mut i = pos;
2155        while i < self.content.len() {
2156            match self.content.as_bytes()[i] as char {
2157                ',' => return i + 1,
2158                '\n' => return i + 1,
2159                _ => i += 1,
2160            }
2161        }
2162        pos
2163    }
2164    
2165    fn get_indentation(&self, pos: usize) -> String {
2166        // Find the start of the current line
2167        let mut line_start = pos;
2168        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
2169            line_start -= 1;
2170        }
2171        
2172        // Count spaces/tabs at the start of the line
2173        let mut indent = String::new();
2174        let mut i = line_start;
2175        while i < self.content.len() {
2176            match self.content.as_bytes()[i] as char {
2177                ' ' | '\t' => {
2178                    indent.push(self.content.as_bytes()[i] as char);
2179                    i += 1;
2180                }
2181                _ => break,
2182            }
2183        }
2184        
2185        // If we're inserting in an empty struct/enum, add default indentation
2186        if indent.is_empty() {
2187            "    ".to_string()
2188        } else {
2189            indent
2190        }
2191    }
2192    
2193    pub fn to_string(&self) -> String {
2194        self.content.clone()
2195    }
2196
2197    /// Get a reference to the syntax tree (for revert operations)
2198    pub fn get_syntax_tree(&self) -> &syn::File {
2199        &self.syntax_tree
2200    }
2201
2202    /// Replace a range of bytes with new content (for revert operations)
2203    pub fn replace_range(&mut self, start: usize, end: usize, new_content: &str) -> Result<()> {
2204        if start > end || end > self.content.len() {
2205            anyhow::bail!("Invalid range: {}..{} (content length: {})", start, end, self.content.len());
2206        }
2207
2208        self.content.replace_range(start..end, new_content);
2209
2210        // Re-parse the syntax tree
2211        self.syntax_tree = syn::parse_str(&self.content)
2212            .context("Failed to re-parse after replace_range")?;
2213        self.line_offsets = Self::compute_line_offsets(&self.content);
2214
2215        Ok(())
2216    }
2217
2218    /// Find all locations where a field appears in the codebase
2219    pub fn find_field_locations(&self, field_name: &str) -> Result<Vec<crate::operations::FieldLocation>> {
2220        use syn::visit::Visit;
2221        use crate::operations::{FieldLocation, FieldContext};
2222
2223        let mut locations = Vec::new();
2224
2225        // Search struct definitions
2226        for item in &self.syntax_tree.items {
2227            if let Item::Struct(s) = item {
2228                if let Fields::Named(ref fields) = s.fields {
2229                    for field in &fields.named {
2230                        if let Some(ident) = &field.ident {
2231                            if ident == field_name {
2232                                let field_type = quote::quote!(#field).to_string()
2233                                    .split(':')
2234                                    .nth(1)
2235                                    .map(|s| s.trim().to_string())
2236                                    .unwrap_or_else(|| "unknown".to_string());
2237                                locations.push(FieldLocation {
2238                                    file_path: String::new(),
2239                                    line: s.span().start().line,
2240                                    context: FieldContext::StructDefinition {
2241                                        struct_name: s.ident.to_string(),
2242                                        field_type,
2243                                    },
2244                                });
2245                            }
2246                        }
2247                    }
2248                }
2249            }
2250
2251            // Search enum variants
2252            if let Item::Enum(e) = item {
2253                for variant in &e.variants {
2254                    if let Fields::Named(ref fields) = variant.fields {
2255                        for field in &fields.named {
2256                            if let Some(ident) = &field.ident {
2257                                if ident == field_name {
2258                                    let field_type = quote::quote!(#field).to_string()
2259                                        .split(':')
2260                                        .nth(1)
2261                                        .map(|s| s.trim().to_string())
2262                                        .unwrap_or_else(|| "unknown".to_string());
2263                                    locations.push(FieldLocation {
2264                                        file_path: String::new(),
2265                                        line: variant.span().start().line,
2266                                        context: FieldContext::EnumVariantDefinition {
2267                                            enum_name: e.ident.to_string(),
2268                                            variant_name: variant.ident.to_string(),
2269                                            field_type,
2270                                        },
2271                                    });
2272                                }
2273                            }
2274                        }
2275                    }
2276                }
2277            }
2278        }
2279
2280        // Search struct literals
2281        struct LiteralVisitor<'a> {
2282            field_name: &'a str,
2283            locations: Vec<FieldLocation>,
2284        }
2285
2286        impl<'ast, 'a> Visit<'ast> for LiteralVisitor<'a> {
2287            fn visit_expr(&mut self, node: &'ast Expr) {
2288                if let Expr::Struct(expr_struct) = node {
2289                    // Check if this struct literal has the field
2290                    for field_value in &expr_struct.fields {
2291                        if let syn::Member::Named(ident) = &field_value.member {
2292                            if ident == self.field_name {
2293                                let struct_name = expr_struct.path.segments.iter()
2294                                    .map(|seg| seg.ident.to_string())
2295                                    .collect::<Vec<_>>()
2296                                    .join("::");
2297
2298                                self.locations.push(FieldLocation {
2299                                    file_path: String::new(),
2300                                    line: expr_struct.span().start().line,
2301                                    context: FieldContext::StructLiteral {
2302                                        struct_name,
2303                                    },
2304                                });
2305                                break;
2306                            }
2307                        }
2308                    }
2309                }
2310                syn::visit::visit_expr(self, node);
2311            }
2312        }
2313
2314        let mut visitor = LiteralVisitor {
2315            field_name,
2316            locations: Vec::new(),
2317        };
2318
2319        visitor.visit_file(&self.syntax_tree);
2320        locations.extend(visitor.locations);
2321
2322        Ok(locations)
2323    }
2324
2325    /// Inspect and list AST nodes (e.g., struct literals) in the file
2326    pub fn inspect(&self, node_type: Option<&str>, name_filter: Option<&str>, variant_filter: Option<&str>, include_comments: bool) -> Result<Vec<crate::operations::InspectResult>> {
2327        use syn::visit::Visit;
2328        use crate::operations::InspectResult;
2329
2330        let mut results = Vec::new();
2331
2332        // If node_type is None, search all node types
2333        if node_type.is_none() {
2334            let all_types = vec![
2335                "struct", "enum", "function", "impl-method", "trait", "const", "static", "type-alias", "mod",
2336                "struct-literal", "match-arm", "enum-usage", "function-call", "method-call", "macro-call", "identifier", "type-ref",
2337            ];
2338            for nt in all_types {
2339                let mut type_results = self.inspect(Some(nt), name_filter, variant_filter, include_comments)?;
2340                results.append(&mut type_results);
2341            }
2342            return Ok(results);
2343        }
2344
2345        let node_type = node_type.unwrap(); // Safe because we checked is_none above
2346
2347        match node_type {
2348            "struct-literal" => {
2349                // Find all struct literal expressions
2350                struct StructLiteralVisitor<'a> {
2351                    results: &'a mut Vec<InspectResult>,
2352                    name_filter: Option<&'a str>,
2353                    editor: &'a RustEditor,
2354                    include_comments: bool,
2355                }
2356
2357                impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
2358                    fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2359                        // Try to parse macro contents as expressions to find struct literals inside
2360                        // This handles cases like: vec![View::Rectangle { ... }]
2361                        use syn::parse::Parser;
2362
2363                        // Try to parse the macro tokens as a punctuated list of expressions (common in vec!, etc.)
2364                        if let Ok(exprs) = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated
2365                            .parse2(node.mac.tokens.clone())
2366                        {
2367                            for expr in exprs.iter() {
2368                                syn::visit::visit_expr(self, expr);
2369                            }
2370                        }
2371
2372                        // Continue with default visit behavior
2373                        syn::visit::visit_expr_macro(self, node);
2374                    }
2375
2376                    fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
2377                        // Match based on pattern:
2378                        // - "Rectangle" → only Rectangle { ... } (no :: prefix)
2379                        // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
2380                        // - "View::Rectangle" → exact match only View::Rectangle
2381
2382                        let filter = match self.name_filter {
2383                            Some(f) => f,
2384                            None => {
2385                                // No filter - match anything
2386                                // If path has multiple segments, include the full qualified path
2387                                let struct_name = if node.path.segments.len() > 1 {
2388                                    node.path.segments.iter()
2389                                        .map(|seg| seg.ident.to_string())
2390                                        .collect::<Vec<_>>()
2391                                        .join("::")
2392                                } else {
2393                                    node.path.segments.last()
2394                                        .map(|seg| seg.ident.to_string())
2395                                        .unwrap_or_default()
2396                                };
2397
2398                                let snippet = self.editor.format_expr_struct(node);
2399                                let location = self.editor.span_to_location(node.span());
2400
2401                        // Extract preceding comment if requested
2402                        let preceding_comment = if self.include_comments {
2403                            extract_preceding_comment(&self.editor.content, location.line)
2404                        } else {
2405                            None
2406                        };
2407
2408                                // Extract preceding comment if requested
2409                                let preceding_comment = if self.include_comments {
2410                                    extract_preceding_comment(&self.editor.content, location.line)
2411                                } else {
2412                                    None
2413                                };
2414
2415                                self.results.push(InspectResult {
2416                                    file_path: String::new(),
2417                                    node_type: "struct-literal".to_string(),
2418                                    identifier: struct_name,
2419                                    location,
2420                                    snippet,
2421                                    preceding_comment,
2422                                });
2423
2424                                syn::visit::visit_expr_struct(self, node);
2425                                return;
2426                            }
2427                        };
2428
2429                        // Check if this struct literal matches the filter pattern
2430                        let matches = if filter.contains("::") {
2431                            // Pattern contains :: - check for exact or wildcard match
2432                            if filter.starts_with("*::") {
2433                                // Wildcard: *::Rectangle matches any path ending with Rectangle
2434                                let target_name = &filter[3..]; // Skip "*::"
2435                                node.path.segments.last()
2436                                    .map(|seg| seg.ident.to_string() == target_name)
2437                                    .unwrap_or(false)
2438                            } else {
2439                                // Exact path match: View::Rectangle
2440                                let path_str = node.path.segments.iter()
2441                                    .map(|seg| seg.ident.to_string())
2442                                    .collect::<Vec<_>>()
2443                                    .join("::");
2444                                path_str == filter
2445                            }
2446                        } else {
2447                            // No :: in pattern - eagerly match any path ending with this name
2448                            // This matches both "Rectangle" and "View::Rectangle" when searching for "Rectangle"
2449                            node.path.segments.last()
2450                                .map(|seg| seg.ident.to_string() == filter)
2451                                .unwrap_or(false)
2452                        };
2453
2454                        if !matches {
2455                            syn::visit::visit_expr_struct(self, node);
2456                            return;
2457                        }
2458
2459                        // Get the struct name for the identifier
2460                        // If path has multiple segments, include the full qualified path
2461                        let struct_name = if node.path.segments.len() > 1 {
2462                            node.path.segments.iter()
2463                                .map(|seg| seg.ident.to_string())
2464                                .collect::<Vec<_>>()
2465                                .join("::")
2466                        } else {
2467                            node.path.segments.last()
2468                                .map(|seg| seg.ident.to_string())
2469                                .unwrap_or_default()
2470                        };
2471
2472                        // Format the struct literal
2473                        let snippet = self.editor.format_expr_struct(node);
2474                        let location = self.editor.span_to_location(node.span());
2475
2476                        // Extract preceding comment if requested
2477                        let preceding_comment = if self.include_comments {
2478                            extract_preceding_comment(&self.editor.content, location.line)
2479                        } else {
2480                            None
2481                        };
2482
2483                        // Extract preceding comment if requested
2484                        let preceding_comment = if self.include_comments {
2485                            extract_preceding_comment(&self.editor.content, location.line)
2486                        } else {
2487                            None
2488                        };
2489
2490                        self.results.push(InspectResult {
2491                            file_path: String::new(), // Will be filled in by caller
2492                            node_type: "struct-literal".to_string(),
2493                            identifier: struct_name,
2494                            location,
2495                            snippet,
2496                            preceding_comment,
2497                        });
2498
2499                        // Continue visiting nested expressions
2500                        syn::visit::visit_expr_struct(self, node);
2501                    }
2502                }
2503
2504                let mut visitor = StructLiteralVisitor {
2505                    results: &mut results,
2506                    name_filter,
2507                    editor: self,
2508                    include_comments,
2509                };
2510
2511                // Visit all items in the file
2512                for item in &self.syntax_tree.items {
2513                    syn::visit::visit_item(&mut visitor, item);
2514                }
2515            }
2516            "match-arm" => {
2517                // Find all match arms
2518                struct MatchArmVisitor<'a> {
2519                    results: &'a mut Vec<InspectResult>,
2520                    pattern_filter: Option<&'a str>,
2521                    editor: &'a RustEditor,
2522                    include_comments: bool,
2523                }
2524
2525                impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
2526                    fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
2527                        // Iterate through all arms in this match expression
2528                        for arm in &node.arms {
2529                            // Convert pattern to string for matching
2530                            let pat = &arm.pat;
2531                            let pattern_str = quote::quote!(#pat).to_string();
2532
2533                            // Apply pattern filter if specified
2534                            if let Some(filter) = self.pattern_filter {
2535                                // Normalize both for comparison (remove spaces)
2536                                let normalized_pattern = pattern_str.replace(" ", "");
2537                                let normalized_filter = filter.replace(" ", "");
2538
2539                                if !normalized_pattern.contains(&normalized_filter) {
2540                                    continue;
2541                                }
2542                            }
2543
2544                            // Format the match arm (pattern => body)
2545                            let snippet = self.editor.format_match_arm(arm);
2546                            let location = self.editor.span_to_location(arm.span());
2547
2548                        // Extract preceding comment if requested
2549                        let preceding_comment = if self.include_comments {
2550                            extract_preceding_comment(&self.editor.content, location.line)
2551                        } else {
2552                            None
2553                        };
2554
2555                            // Extract preceding comment if requested
2556                            let preceding_comment = if self.include_comments {
2557                                extract_preceding_comment(&self.editor.content, location.line)
2558                            } else {
2559                                None
2560                            };
2561
2562                            self.results.push(InspectResult {
2563                                file_path: String::new(), // Will be filled in by caller
2564                                node_type: "match-arm".to_string(),
2565                                identifier: pattern_str.replace(" ", ""),
2566                                location,
2567                                snippet,
2568                                preceding_comment,
2569                            });
2570                        }
2571
2572                        // Continue visiting nested expressions
2573                        syn::visit::visit_expr_match(self, node);
2574                    }
2575                }
2576
2577                let mut visitor = MatchArmVisitor {
2578                    results: &mut results,
2579                    pattern_filter: name_filter,
2580                    editor: self,
2581                    include_comments,
2582                };
2583
2584                // Visit all items in the file
2585                for item in &self.syntax_tree.items {
2586                    syn::visit::visit_item(&mut visitor, item);
2587                }
2588            }
2589            "enum-usage" => {
2590                // Find all enum variant usages (paths like Operator::Error)
2591                struct EnumUsageVisitor<'a> {
2592                    results: &'a mut Vec<InspectResult>,
2593                    path_filter: Option<&'a str>,
2594                    editor: &'a RustEditor,
2595                    include_comments: bool,
2596                }
2597
2598                impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
2599                    fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
2600                        // Convert path to string
2601                        let path = &node.path;
2602                        let path_str = quote::quote!(#path).to_string();
2603
2604                        // Apply path filter if specified
2605                        if let Some(filter) = self.path_filter {
2606                            // Normalize both for comparison (remove spaces)
2607                            let normalized_path = path_str.replace(" ", "");
2608                            let normalized_filter = filter.replace(" ", "");
2609
2610                            if !normalized_path.contains(&normalized_filter) {
2611                                syn::visit::visit_expr_path(self, node);
2612                                return;
2613                            }
2614                        }
2615
2616                        // Format the path expression
2617                        let snippet = self.editor.format_expr_path(node);
2618                        let location = self.editor.span_to_location(node.span());
2619
2620                        // Extract preceding comment if requested
2621                        let preceding_comment = if self.include_comments {
2622                            extract_preceding_comment(&self.editor.content, location.line)
2623                        } else {
2624                            None
2625                        };
2626
2627                        // Extract preceding comment if requested
2628                        let preceding_comment = if self.include_comments {
2629                            extract_preceding_comment(&self.editor.content, location.line)
2630                        } else {
2631                            None
2632                        };
2633
2634                        self.results.push(InspectResult {
2635                            file_path: String::new(), // Will be filled in by caller
2636                            node_type: "enum-usage".to_string(),
2637                            identifier: path_str.replace(" ", ""),
2638                            location,
2639                            snippet,
2640                            preceding_comment,
2641                        });
2642
2643                        // Continue visiting nested expressions
2644                        syn::visit::visit_expr_path(self, node);
2645                    }
2646                }
2647
2648                let mut visitor = EnumUsageVisitor {
2649                    results: &mut results,
2650                    path_filter: name_filter,
2651                    editor: self,
2652                    include_comments,
2653                };
2654
2655                // Visit all items in the file
2656                for item in &self.syntax_tree.items {
2657                    syn::visit::visit_item(&mut visitor, item);
2658                }
2659            }
2660            "function-call" => {
2661                // Find all function call expressions
2662                struct FunctionCallVisitor<'a> {
2663                    results: &'a mut Vec<InspectResult>,
2664                    name_filter: Option<&'a str>,
2665                    editor: &'a RustEditor,
2666                    include_comments: bool,
2667                }
2668
2669                impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
2670                    fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
2671                        // Extract function name from the call expression
2672                        let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
2673                            // Get the last segment of the path as the function name
2674                            expr_path.path.segments.last()
2675                                .map(|seg| seg.ident.to_string())
2676                                .unwrap_or_default()
2677                        } else {
2678                            // For other expression types, use quote to convert to string
2679                            quote::quote!(#node.func).to_string()
2680                        };
2681
2682                        // Apply name filter if specified
2683                        if let Some(filter) = self.name_filter {
2684                            if func_name != filter {
2685                                syn::visit::visit_expr_call(self, node);
2686                                return;
2687                            }
2688                        }
2689
2690                        // Format the function call
2691                        let snippet = self.editor.format_expr_call(node);
2692                        let location = self.editor.span_to_location(node.span());
2693
2694                        // Extract preceding comment if requested
2695                        let preceding_comment = if self.include_comments {
2696                            extract_preceding_comment(&self.editor.content, location.line)
2697                        } else {
2698                            None
2699                        };
2700
2701                        // Extract preceding comment if requested
2702                        let preceding_comment = if self.include_comments {
2703                            extract_preceding_comment(&self.editor.content, location.line)
2704                        } else {
2705                            None
2706                        };
2707
2708                        self.results.push(InspectResult {
2709                            file_path: String::new(), // Will be filled in by caller
2710                            node_type: "function-call".to_string(),
2711                            identifier: func_name,
2712                            location,
2713                            snippet,
2714                            preceding_comment,
2715                        });
2716
2717                        // Continue visiting nested expressions
2718                        syn::visit::visit_expr_call(self, node);
2719                    }
2720                }
2721
2722                let mut visitor = FunctionCallVisitor {
2723                    results: &mut results,
2724                    name_filter,
2725                    editor: self,
2726                    include_comments,
2727                };
2728
2729                // Visit all items in the file
2730                for item in &self.syntax_tree.items {
2731                    syn::visit::visit_item(&mut visitor, item);
2732                }
2733            }
2734            "trait-method" => {
2735                // Find methods within trait definitions
2736                struct TraitMethodVisitor<'a> {
2737                    results: &'a mut Vec<InspectResult>,
2738                    name_filter: Option<&'a str>,
2739                    editor: &'a RustEditor,
2740                    include_comments: bool,
2741                    current_trait_name: Option<String>,
2742                }
2743
2744                impl<'ast, 'a> Visit<'ast> for TraitMethodVisitor<'a> {
2745                    fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
2746                        // Track which trait this is
2747                        let prev_trait_name = self.current_trait_name.clone();
2748                        self.current_trait_name = Some(node.ident.to_string());
2749
2750                        syn::visit::visit_item_trait(self, node);
2751
2752                        self.current_trait_name = prev_trait_name;
2753                    }
2754
2755                    fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
2756                        let method_name = node.sig.ident.to_string();
2757
2758                        // Create identifier with trait context if available
2759                        let identifier = if let Some(ref trait_name) = self.current_trait_name {
2760                            format!("{}::{}", trait_name, method_name)
2761                        } else {
2762                            method_name.clone()
2763                        };
2764
2765                        // Apply name filter if specified
2766                        if let Some(filter) = self.name_filter {
2767                            // Support filtering by "Trait::method" or just "method"
2768                            if !identifier.contains(filter) && method_name != filter {
2769                                syn::visit::visit_trait_item_fn(self, node);
2770                                return;
2771                            }
2772                        }
2773
2774                        // Format the method definition
2775                        let snippet = self.editor.format_trait_item_fn(node);
2776                        let location = self.editor.span_to_location(node.span());
2777
2778                        // Extract preceding comment if requested
2779                        let preceding_comment = if self.include_comments {
2780                            extract_preceding_comment(&self.editor.content, location.line)
2781                        } else {
2782                            None
2783                        };
2784
2785                        self.results.push(InspectResult {
2786                            file_path: String::new(),
2787                            node_type: "trait-method".to_string(),
2788                            identifier,
2789                            location,
2790                            snippet,
2791                            preceding_comment,
2792                        });
2793
2794                        syn::visit::visit_trait_item_fn(self, node);
2795                    }
2796                }
2797
2798                let mut visitor = TraitMethodVisitor {
2799                    results: &mut results,
2800                    name_filter,
2801                    editor: self,
2802                    include_comments,
2803                    current_trait_name: None,
2804                };
2805
2806                for item in &self.syntax_tree.items {
2807                    syn::visit::visit_item(&mut visitor, item);
2808                }
2809            }
2810            "method-call" => {
2811                // Find all method call expressions
2812                struct MethodCallVisitor<'a> {
2813                    results: &'a mut Vec<InspectResult>,
2814                    name_filter: Option<&'a str>,
2815                    editor: &'a RustEditor,
2816                    include_comments: bool,
2817                }
2818
2819                impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
2820                    fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
2821                        // Extract method name
2822                        let method_name = node.method.to_string();
2823
2824                        // Apply name filter if specified
2825                        if let Some(filter) = self.name_filter {
2826                            if method_name != filter {
2827                                syn::visit::visit_expr_method_call(self, node);
2828                                return;
2829                            }
2830                        }
2831
2832                        // Format the method call
2833                        let snippet = self.editor.format_expr_method_call(node);
2834                        let location = self.editor.span_to_location(node.span());
2835
2836                        // Extract preceding comment if requested
2837                        let preceding_comment = if self.include_comments {
2838                            extract_preceding_comment(&self.editor.content, location.line)
2839                        } else {
2840                            None
2841                        };
2842
2843                        self.results.push(InspectResult {
2844                            file_path: String::new(), // Will be filled in by caller
2845                            node_type: "method-call".to_string(),
2846                            identifier: method_name,
2847                            location,
2848                            snippet,
2849                            preceding_comment,
2850                        });
2851
2852                        // Continue visiting nested expressions
2853                        syn::visit::visit_expr_method_call(self, node);
2854                    }
2855                }
2856
2857                let mut visitor = MethodCallVisitor {
2858                    results: &mut results,
2859                    name_filter,
2860                    editor: self,
2861                    include_comments,
2862                };
2863
2864                // Visit all items in the file
2865                for item in &self.syntax_tree.items {
2866                    syn::visit::visit_item(&mut visitor, item);
2867                }
2868            }
2869            "identifier" => {
2870                // Find all identifier references
2871                struct IdentifierVisitor<'a> {
2872                    results: &'a mut Vec<InspectResult>,
2873                    name_filter: Option<&'a str>,
2874                    editor: &'a RustEditor,
2875                    include_comments: bool,
2876                }
2877
2878                impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2879                    fn visit_ident(&mut self, node: &'ast syn::Ident) {
2880                        // Extract identifier name
2881                        let ident_name = node.to_string();
2882
2883                        // Apply name filter if specified
2884                        if let Some(filter) = self.name_filter {
2885                            if ident_name != filter {
2886                                syn::visit::visit_ident(self, node);
2887                                return;
2888                            }
2889                        }
2890
2891                        // Format the identifier
2892                        let snippet = self.editor.format_ident(node);
2893                        let location = self.editor.span_to_location(node.span());
2894
2895                        // Extract preceding comment if requested
2896                        let preceding_comment = if self.include_comments {
2897                            extract_preceding_comment(&self.editor.content, location.line)
2898                        } else {
2899                            None
2900                        };
2901
2902                        self.results.push(InspectResult {
2903                            file_path: String::new(), // Will be filled in by caller
2904                            node_type: "identifier".to_string(),
2905                            identifier: ident_name,
2906                            location,
2907                            snippet,
2908                            preceding_comment,
2909                        });
2910
2911                        // Continue visiting
2912                        syn::visit::visit_ident(self, node);
2913                    }
2914                }
2915
2916                let mut visitor = IdentifierVisitor {
2917                    results: &mut results,
2918                    name_filter,
2919                    editor: self,
2920                    include_comments,
2921                };
2922
2923                // Visit all items in the file
2924                for item in &self.syntax_tree.items {
2925                    syn::visit::visit_item(&mut visitor, item);
2926                }
2927            }
2928            "type-ref" => {
2929                // Find all type path usages
2930                struct TypeRefVisitor<'a> {
2931                    results: &'a mut Vec<InspectResult>,
2932                    name_filter: Option<&'a str>,
2933                    editor: &'a RustEditor,
2934                    include_comments: bool,
2935                }
2936
2937                impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2938                    fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2939                        // Extract type name (last segment of path)
2940                        let type_name = node.path.segments.last()
2941                            .map(|seg| seg.ident.to_string())
2942                            .unwrap_or_default();
2943
2944                        // Apply name filter if specified
2945                        if let Some(filter) = self.name_filter {
2946                            if type_name != filter {
2947                                syn::visit::visit_type_path(self, node);
2948                                return;
2949                            }
2950                        }
2951
2952                        // Format the type path
2953                        let snippet = self.editor.format_type_path(node);
2954                        let location = self.editor.span_to_location(node.span());
2955
2956                        // Extract preceding comment if requested
2957                        let preceding_comment = if self.include_comments {
2958                            extract_preceding_comment(&self.editor.content, location.line)
2959                        } else {
2960                            None
2961                        };
2962
2963                        // Get full path for identifier
2964                        let path = &node.path;
2965                        let path_str = quote::quote!(#path).to_string();
2966
2967                        self.results.push(InspectResult {
2968                            file_path: String::new(), // Will be filled in by caller
2969                            node_type: "type-ref".to_string(),
2970                            identifier: path_str.replace(" ", ""),
2971                            location,
2972                            snippet,
2973                            preceding_comment,
2974                        });
2975
2976                        // Continue visiting
2977                        syn::visit::visit_type_path(self, node);
2978                    }
2979                }
2980
2981                let mut visitor = TypeRefVisitor {
2982                    results: &mut results,
2983                    name_filter,
2984                    editor: self,
2985                    include_comments,
2986                };
2987
2988                // Visit all items in the file
2989                for item in &self.syntax_tree.items {
2990                    syn::visit::visit_item(&mut visitor, item);
2991                }
2992            }
2993            "macro-call" => {
2994                // Find all macro call expressions
2995                struct MacroCallVisitor<'a> {
2996                    results: &'a mut Vec<InspectResult>,
2997                    name_filter: Option<&'a str>,
2998                    editor: &'a RustEditor,
2999                    include_comments: bool,
3000                }
3001
3002                impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
3003                    fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
3004                        // Extract macro name from the path
3005                        let macro_name = node.mac.path.segments.last()
3006                            .map(|seg| seg.ident.to_string())
3007                            .unwrap_or_default();
3008
3009                        // Apply name filter if specified
3010                        if let Some(filter) = self.name_filter {
3011                            if macro_name != filter {
3012                                syn::visit::visit_expr_macro(self, node);
3013                                return;
3014                            }
3015                        }
3016
3017                        // Format the macro call
3018                        let snippet = self.editor.format_expr_macro(node);
3019                        let location = self.editor.span_to_location(node.span());
3020
3021                        // Extract preceding comment if requested
3022                        let preceding_comment = if self.include_comments {
3023                            extract_preceding_comment(&self.editor.content, location.line)
3024                        } else {
3025                            None
3026                        };
3027
3028                        self.results.push(InspectResult {
3029                            file_path: String::new(), // Will be filled in by caller
3030                            node_type: "macro-call".to_string(),
3031                            identifier: macro_name,
3032                            location,
3033                            snippet,
3034                            preceding_comment,
3035                        });
3036
3037                        // Continue visiting nested expressions
3038                        syn::visit::visit_expr_macro(self, node);
3039                    }
3040
3041                    fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
3042                        // Also catch macro calls at statement level (e.g., println! as statement)
3043                        if let syn::Stmt::Macro(macro_stmt) = node {
3044                            let macro_name = macro_stmt.mac.path.segments.last()
3045                                .map(|seg| seg.ident.to_string())
3046                                .unwrap_or_default();
3047
3048                            // Apply name filter if specified
3049                            if let Some(filter) = self.name_filter {
3050                                if macro_name != filter {
3051                                    syn::visit::visit_stmt(self, node);
3052                                    return;
3053                                }
3054                            }
3055
3056                            // Format the macro call
3057                            let snippet = self.editor.format_stmt_macro(macro_stmt);
3058                            let location = self.editor.span_to_location(macro_stmt.span());
3059
3060                        // Extract preceding comment if requested
3061                        let preceding_comment = if self.include_comments {
3062                            extract_preceding_comment(&self.editor.content, location.line)
3063                        } else {
3064                            None
3065                        };
3066
3067                            self.results.push(InspectResult {
3068                                file_path: String::new(), // Will be filled in by caller
3069                                node_type: "macro-call".to_string(),
3070                                identifier: macro_name,
3071                                location,
3072                                snippet,
3073                                preceding_comment,
3074                            });
3075                        }
3076
3077                        // Continue visiting
3078                        syn::visit::visit_stmt(self, node);
3079                    }
3080                }
3081
3082                let mut visitor = MacroCallVisitor {
3083                    results: &mut results,
3084                    name_filter,
3085                    editor: self,
3086                    include_comments,
3087                };
3088
3089                // Visit all items in the file
3090                for item in &self.syntax_tree.items {
3091                    syn::visit::visit_item(&mut visitor, item);
3092                }
3093            }
3094            "struct" => {
3095                // Find all struct definitions
3096                struct StructDefVisitor<'a> {
3097                    results: &'a mut Vec<InspectResult>,
3098                    name_filter: Option<&'a str>,
3099                    editor: &'a RustEditor,
3100                    include_comments: bool,
3101                }
3102
3103                impl<'ast, 'a> Visit<'ast> for StructDefVisitor<'a> {
3104                    fn visit_item_struct(&mut self, node: &'ast syn::ItemStruct) {
3105                        let struct_name = node.ident.to_string();
3106
3107                        // Apply name filter if specified
3108                        if let Some(filter) = self.name_filter {
3109                            if struct_name != filter {
3110                                syn::visit::visit_item_struct(self, node);
3111                                return;
3112                            }
3113                        }
3114
3115                        // Format the struct definition
3116                        let snippet = self.editor.format_item_struct(node);
3117                        let location = self.editor.span_to_location(node.span());
3118
3119                        // Extract preceding comment if requested
3120                        let preceding_comment = if self.include_comments {
3121                            extract_preceding_comment(&self.editor.content, location.line)
3122                        } else {
3123                            None
3124                        };
3125
3126                        self.results.push(InspectResult {
3127                            file_path: String::new(),
3128                            node_type: "struct".to_string(),
3129                            identifier: struct_name,
3130                            location,
3131                            snippet,
3132                            preceding_comment,
3133                        });
3134
3135                        syn::visit::visit_item_struct(self, node);
3136                    }
3137                }
3138
3139                let mut visitor = StructDefVisitor {
3140                    results: &mut results,
3141                    name_filter,
3142                    editor: self,
3143                    include_comments,
3144                };
3145
3146                for item in &self.syntax_tree.items {
3147                    syn::visit::visit_item(&mut visitor, item);
3148                }
3149            }
3150            "enum" => {
3151                // Find all enum definitions
3152                // Supports:
3153                // 1. --variant Rectangle (all enums with Rectangle variant)
3154                // 2. --name View --variant Rectangle (View enum, Rectangle variant only)
3155                // 3. --name View::Rectangle (:: syntax auto-detects variant filter)
3156                // 4. --name *::Rectangle (wildcard: any enum with Rectangle variant)
3157                struct EnumDefVisitor<'a> {
3158                    results: &'a mut Vec<InspectResult>,
3159                    name_filter: Option<&'a str>,
3160                    variant_filter: Option<&'a str>,
3161                    editor: &'a RustEditor,
3162                    include_comments: bool,
3163                }
3164
3165                impl<'ast, 'a> Visit<'ast> for EnumDefVisitor<'a> {
3166                    fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
3167                        let enum_name = node.ident.to_string();
3168
3169                        // Parse name_filter for :: syntax
3170                        let (enum_name_filter, implicit_variant_filter) = if let Some(filter) = self.name_filter {
3171                            if filter.contains("::") {
3172                                // View::Rectangle or *::Rectangle
3173                                let parts: Vec<&str> = filter.split("::").collect();
3174                                if parts.len() == 2 {
3175                                    if parts[0] == "*" {
3176                                        // *::Rectangle - match any enum with this variant
3177                                        (None, Some(parts[1]))
3178                                    } else {
3179                                        // View::Rectangle - match specific enum with this variant
3180                                        (Some(parts[0]), Some(parts[1]))
3181                                    }
3182                                } else {
3183                                    // Invalid :: syntax, treat as exact enum name
3184                                    (Some(filter), None)
3185                                }
3186                            } else {
3187                                // Simple enum name filter
3188                                (Some(filter), None)
3189                            }
3190                        } else {
3191                            (None, None)
3192                        };
3193
3194                        // Determine which variant filter to use (explicit --variant flag or implicit from ::)
3195                        let effective_variant_filter = self.variant_filter.or(implicit_variant_filter);
3196
3197                        // Apply enum name filter if specified
3198                        if let Some(filter) = enum_name_filter {
3199                            if enum_name != filter {
3200                                syn::visit::visit_item_enum(self, node);
3201                                return;
3202                            }
3203                        }
3204
3205                        // If variant filter is specified, check if this enum has that variant
3206                        if let Some(variant_name) = effective_variant_filter {
3207                            let has_variant = node.variants.iter().any(|v| v.ident.to_string() == variant_name);
3208                            if !has_variant {
3209                                syn::visit::visit_item_enum(self, node);
3210                                return;
3211                            }
3212                        }
3213
3214                        // Format the enum definition
3215                        let snippet = if let Some(variant_name) = effective_variant_filter {
3216                            // Filter to show only the matching variant
3217                            self.editor.format_item_enum_variant_only(node, variant_name)
3218                        } else {
3219                            self.editor.format_item_enum(node)
3220                        };
3221
3222                        let location = self.editor.span_to_location(node.span());
3223
3224                        // Extract preceding comment if requested
3225                        let preceding_comment = if self.include_comments {
3226                            extract_preceding_comment(&self.editor.content, location.line)
3227                        } else {
3228                            None
3229                        };
3230
3231                        self.results.push(InspectResult {
3232                            file_path: String::new(),
3233                            node_type: "enum".to_string(),
3234                            identifier: enum_name,
3235                            location,
3236                            snippet,
3237                            preceding_comment,
3238                        });
3239
3240                        syn::visit::visit_item_enum(self, node);
3241                    }
3242                }
3243
3244                let mut visitor = EnumDefVisitor {
3245                    results: &mut results,
3246                    name_filter,
3247                    variant_filter,
3248                    editor: self,
3249                    include_comments,
3250                };
3251
3252                for item in &self.syntax_tree.items {
3253                    syn::visit::visit_item(&mut visitor, item);
3254                }
3255            }
3256            "function" => {
3257                // Find all function definitions
3258                struct FunctionDefVisitor<'a> {
3259                    results: &'a mut Vec<InspectResult>,
3260                    name_filter: Option<&'a str>,
3261                    editor: &'a RustEditor,
3262                    include_comments: bool,
3263                }
3264
3265                impl<'ast, 'a> Visit<'ast> for FunctionDefVisitor<'a> {
3266                    fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
3267                        let fn_name = node.sig.ident.to_string();
3268
3269                        // Apply name filter if specified
3270                        if let Some(filter) = self.name_filter {
3271                            if fn_name != filter {
3272                                syn::visit::visit_item_fn(self, node);
3273                                return;
3274                            }
3275                        }
3276
3277                        // Format the function definition
3278                        let snippet = self.editor.format_item_fn(node);
3279                        let location = self.editor.span_to_location(node.span());
3280
3281                        // Extract preceding comment if requested
3282                        let preceding_comment = if self.include_comments {
3283                            extract_preceding_comment(&self.editor.content, location.line)
3284                        } else {
3285                            None
3286                        };
3287
3288                        self.results.push(InspectResult {
3289                            file_path: String::new(),
3290                            node_type: "function".to_string(),
3291                            identifier: fn_name,
3292                            location,
3293                            snippet,
3294                            preceding_comment,
3295                        });
3296
3297                        syn::visit::visit_item_fn(self, node);
3298                    }
3299                }
3300
3301                let mut visitor = FunctionDefVisitor {
3302                    results: &mut results,
3303                    name_filter,
3304                    editor: self,
3305                    include_comments,
3306                };
3307
3308                for item in &self.syntax_tree.items {
3309                    syn::visit::visit_item(&mut visitor, item);
3310                }
3311            }
3312            "impl-method" => {
3313                // Find methods within impl blocks
3314                struct ImplMethodVisitor<'a> {
3315                    results: &'a mut Vec<InspectResult>,
3316                    name_filter: Option<&'a str>,
3317                    editor: &'a RustEditor,
3318                    include_comments: bool,
3319                    current_impl_type: Option<String>,
3320                }
3321
3322                impl<'ast, 'a> Visit<'ast> for ImplMethodVisitor<'a> {
3323                    fn visit_item_impl(&mut self, node: &'ast syn::ItemImpl) {
3324                        // Track which type this impl is for
3325                        let impl_type = if let syn::Type::Path(type_path) = &*node.self_ty {
3326                            type_path.path.segments.last()
3327                                .map(|seg| seg.ident.to_string())
3328                        } else {
3329                            None
3330                        };
3331
3332                        let prev_impl_type = self.current_impl_type.clone();
3333                        self.current_impl_type = impl_type;
3334
3335                        syn::visit::visit_item_impl(self, node);
3336
3337                        self.current_impl_type = prev_impl_type;
3338                    }
3339
3340                    fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
3341                        let method_name = node.sig.ident.to_string();
3342
3343                        // Create identifier with impl type context if available
3344                        let identifier = if let Some(ref impl_type) = self.current_impl_type {
3345                            format!("{}::{}", impl_type, method_name)
3346                        } else {
3347                            method_name.clone()
3348                        };
3349
3350                        // Apply name filter if specified
3351                        if let Some(filter) = self.name_filter {
3352                            // Support filtering by "Type::method" or just "method"
3353                            if !identifier.contains(filter) && method_name != filter {
3354                                syn::visit::visit_impl_item_fn(self, node);
3355                                return;
3356                            }
3357                        }
3358
3359                        // Format the method definition
3360                        let snippet = self.editor.format_impl_item_fn(node);
3361                        let location = self.editor.span_to_location(node.span());
3362
3363                        // Extract preceding comment if requested
3364                        let preceding_comment = if self.include_comments {
3365                            extract_preceding_comment(&self.editor.content, location.line)
3366                        } else {
3367                            None
3368                        };
3369
3370                        self.results.push(InspectResult {
3371                            file_path: String::new(),
3372                            node_type: "impl-method".to_string(),
3373                            identifier,
3374                            location,
3375                            snippet,
3376                            preceding_comment,
3377                        });
3378
3379                        syn::visit::visit_impl_item_fn(self, node);
3380                    }
3381                }
3382
3383                let mut visitor = ImplMethodVisitor {
3384                    results: &mut results,
3385                    name_filter,
3386                    editor: self,
3387                    include_comments,
3388                    current_impl_type: None,
3389                };
3390
3391                for item in &self.syntax_tree.items {
3392                    syn::visit::visit_item(&mut visitor, item);
3393                }
3394            }
3395            "trait" => {
3396                // Find all trait definitions
3397                struct TraitDefVisitor<'a> {
3398                    results: &'a mut Vec<InspectResult>,
3399                    name_filter: Option<&'a str>,
3400                    editor: &'a RustEditor,
3401                    include_comments: bool,
3402                }
3403
3404                impl<'ast, 'a> Visit<'ast> for TraitDefVisitor<'a> {
3405                    fn visit_item_trait(&mut self, node: &'ast syn::ItemTrait) {
3406                        let trait_name = node.ident.to_string();
3407
3408                        // Apply name filter if specified
3409                        if let Some(filter) = self.name_filter {
3410                            if trait_name != filter {
3411                                syn::visit::visit_item_trait(self, node);
3412                                return;
3413                            }
3414                        }
3415
3416                        // Format the trait definition
3417                        let snippet = self.editor.format_item_trait(node);
3418                        let location = self.editor.span_to_location(node.span());
3419
3420                        // Extract preceding comment if requested
3421                        let preceding_comment = if self.include_comments {
3422                            extract_preceding_comment(&self.editor.content, location.line)
3423                        } else {
3424                            None
3425                        };
3426
3427                        self.results.push(InspectResult {
3428                            file_path: String::new(),
3429                            node_type: "trait".to_string(),
3430                            identifier: trait_name,
3431                            location,
3432                            snippet,
3433                            preceding_comment,
3434                        });
3435
3436                        syn::visit::visit_item_trait(self, node);
3437                    }
3438                }
3439
3440                let mut visitor = TraitDefVisitor {
3441                    results: &mut results,
3442                    name_filter,
3443                    editor: self,
3444                    include_comments,
3445                };
3446
3447                for item in &self.syntax_tree.items {
3448                    syn::visit::visit_item(&mut visitor, item);
3449                }
3450            }
3451            "const" => {
3452                // Find all const definitions
3453                struct ConstDefVisitor<'a> {
3454                    results: &'a mut Vec<InspectResult>,
3455                    name_filter: Option<&'a str>,
3456                    editor: &'a RustEditor,
3457                    include_comments: bool,
3458                }
3459
3460                impl<'ast, 'a> Visit<'ast> for ConstDefVisitor<'a> {
3461                    fn visit_item_const(&mut self, node: &'ast syn::ItemConst) {
3462                        let const_name = node.ident.to_string();
3463
3464                        // Apply name filter if specified
3465                        if let Some(filter) = self.name_filter {
3466                            if const_name != filter {
3467                                syn::visit::visit_item_const(self, node);
3468                                return;
3469                            }
3470                        }
3471
3472                        // Format the const definition
3473                        let snippet = self.editor.format_item_const(node);
3474                        let location = self.editor.span_to_location(node.span());
3475
3476                        // Extract preceding comment if requested
3477                        let preceding_comment = if self.include_comments {
3478                            extract_preceding_comment(&self.editor.content, location.line)
3479                        } else {
3480                            None
3481                        };
3482
3483                        self.results.push(InspectResult {
3484                            file_path: String::new(),
3485                            node_type: "const".to_string(),
3486                            identifier: const_name,
3487                            location,
3488                            snippet,
3489                            preceding_comment,
3490                        });
3491
3492                        syn::visit::visit_item_const(self, node);
3493                    }
3494                }
3495
3496                let mut visitor = ConstDefVisitor {
3497                    results: &mut results,
3498                    name_filter,
3499                    editor: self,
3500                    include_comments,
3501                };
3502
3503                for item in &self.syntax_tree.items {
3504                    syn::visit::visit_item(&mut visitor, item);
3505                }
3506            }
3507            "static" => {
3508                // Find all static definitions
3509                struct StaticDefVisitor<'a> {
3510                    results: &'a mut Vec<InspectResult>,
3511                    name_filter: Option<&'a str>,
3512                    editor: &'a RustEditor,
3513                    include_comments: bool,
3514                }
3515
3516                impl<'ast, 'a> Visit<'ast> for StaticDefVisitor<'a> {
3517                    fn visit_item_static(&mut self, node: &'ast syn::ItemStatic) {
3518                        let static_name = node.ident.to_string();
3519
3520                        // Apply name filter if specified
3521                        if let Some(filter) = self.name_filter {
3522                            if static_name != filter {
3523                                syn::visit::visit_item_static(self, node);
3524                                return;
3525                            }
3526                        }
3527
3528                        // Format the static definition
3529                        let snippet = self.editor.format_item_static(node);
3530                        let location = self.editor.span_to_location(node.span());
3531
3532                        // Extract preceding comment if requested
3533                        let preceding_comment = if self.include_comments {
3534                            extract_preceding_comment(&self.editor.content, location.line)
3535                        } else {
3536                            None
3537                        };
3538
3539                        self.results.push(InspectResult {
3540                            file_path: String::new(),
3541                            node_type: "static".to_string(),
3542                            identifier: static_name,
3543                            location,
3544                            snippet,
3545                            preceding_comment,
3546                        });
3547
3548                        syn::visit::visit_item_static(self, node);
3549                    }
3550                }
3551
3552                let mut visitor = StaticDefVisitor {
3553                    results: &mut results,
3554                    name_filter,
3555                    editor: self,
3556                    include_comments,
3557                };
3558
3559                for item in &self.syntax_tree.items {
3560                    syn::visit::visit_item(&mut visitor, item);
3561                }
3562            }
3563            "type-alias" => {
3564                // Find all type alias definitions
3565                struct TypeAliasVisitor<'a> {
3566                    results: &'a mut Vec<InspectResult>,
3567                    name_filter: Option<&'a str>,
3568                    editor: &'a RustEditor,
3569                    include_comments: bool,
3570                }
3571
3572                impl<'ast, 'a> Visit<'ast> for TypeAliasVisitor<'a> {
3573                    fn visit_item_type(&mut self, node: &'ast syn::ItemType) {
3574                        let type_name = node.ident.to_string();
3575
3576                        // Apply name filter if specified
3577                        if let Some(filter) = self.name_filter {
3578                            if type_name != filter {
3579                                syn::visit::visit_item_type(self, node);
3580                                return;
3581                            }
3582                        }
3583
3584                        // Format the type alias definition
3585                        let snippet = self.editor.format_item_type(node);
3586                        let location = self.editor.span_to_location(node.span());
3587
3588                        // Extract preceding comment if requested
3589                        let preceding_comment = if self.include_comments {
3590                            extract_preceding_comment(&self.editor.content, location.line)
3591                        } else {
3592                            None
3593                        };
3594
3595                        self.results.push(InspectResult {
3596                            file_path: String::new(),
3597                            node_type: "type-alias".to_string(),
3598                            identifier: type_name,
3599                            location,
3600                            snippet,
3601                            preceding_comment,
3602                        });
3603
3604                        syn::visit::visit_item_type(self, node);
3605                    }
3606                }
3607
3608                let mut visitor = TypeAliasVisitor {
3609                    results: &mut results,
3610                    name_filter,
3611                    editor: self,
3612                    include_comments,
3613                };
3614
3615                for item in &self.syntax_tree.items {
3616                    syn::visit::visit_item(&mut visitor, item);
3617                }
3618            }
3619            "mod" => {
3620                // Find all module definitions
3621                struct ModDefVisitor<'a> {
3622                    results: &'a mut Vec<InspectResult>,
3623                    name_filter: Option<&'a str>,
3624                    editor: &'a RustEditor,
3625                    include_comments: bool,
3626                }
3627
3628                impl<'ast, 'a> Visit<'ast> for ModDefVisitor<'a> {
3629                    fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
3630                        let mod_name = node.ident.to_string();
3631
3632                        // Apply name filter if specified
3633                        if let Some(filter) = self.name_filter {
3634                            if mod_name != filter {
3635                                syn::visit::visit_item_mod(self, node);
3636                                return;
3637                            }
3638                        }
3639
3640                        // Format the module definition
3641                        let snippet = self.editor.format_item_mod(node);
3642                        let location = self.editor.span_to_location(node.span());
3643
3644                        // Extract preceding comment if requested
3645                        let preceding_comment = if self.include_comments {
3646                            extract_preceding_comment(&self.editor.content, location.line)
3647                        } else {
3648                            None
3649                        };
3650
3651                        self.results.push(InspectResult {
3652                            file_path: String::new(),
3653                            node_type: "mod".to_string(),
3654                            identifier: mod_name,
3655                            location,
3656                            snippet,
3657                            preceding_comment,
3658                        });
3659
3660                        syn::visit::visit_item_mod(self, node);
3661                    }
3662                }
3663
3664                let mut visitor = ModDefVisitor {
3665                    results: &mut results,
3666                    name_filter,
3667                    editor: self,
3668                    include_comments,
3669                };
3670
3671                for item in &self.syntax_tree.items {
3672                    syn::visit::visit_item(&mut visitor, item);
3673                }
3674            }
3675            _ => anyhow::bail!("Unsupported node type: {}", node_type),
3676        }
3677
3678        Ok(results)
3679    }
3680
3681    /// Format an ExprStruct node as a string - extracts original source
3682    fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
3683        // Extract the original source code from the file content using the span
3684        let start = self.span_to_byte_offset(expr.span().start());
3685        let end = self.span_to_byte_offset(expr.span().end());
3686
3687        // Get the original text and collapse to single line
3688        let original = &self.content[start..end];
3689
3690        // Replace multiple whitespace/newlines with single space for single-line format
3691        original.split_whitespace().collect::<Vec<_>>().join(" ")
3692    }
3693
3694    /// Format a match arm as a string - extracts original source
3695    fn format_match_arm(&self, arm: &syn::Arm) -> String {
3696        // Extract the original source code from the file content using the span
3697        let start = self.span_to_byte_offset(arm.span().start());
3698        let end = self.span_to_byte_offset(arm.span().end());
3699
3700        // Get the original text and collapse to single line
3701        let original = &self.content[start..end];
3702
3703        // Replace multiple whitespace/newlines with single space for single-line format
3704        original.split_whitespace().collect::<Vec<_>>().join(" ")
3705    }
3706
3707    /// Format an ExprPath node as a string - extracts original source
3708    fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
3709        // Extract the original source code from the file content using the span
3710        let start = self.span_to_byte_offset(expr.span().start());
3711        let end = self.span_to_byte_offset(expr.span().end());
3712
3713        // Get the original text and collapse to single line
3714        let original = &self.content[start..end];
3715
3716        // Replace multiple whitespace/newlines with single space for single-line format
3717        original.split_whitespace().collect::<Vec<_>>().join(" ")
3718    }
3719
3720    /// Format an ExprCall node as a string - extracts original source
3721    fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
3722        // Extract the original source code from the file content using the span
3723        let start = self.span_to_byte_offset(expr.span().start());
3724        let end = self.span_to_byte_offset(expr.span().end());
3725
3726        // Get the original text and collapse to single line
3727        let original = &self.content[start..end];
3728
3729        // Replace multiple whitespace/newlines with single space for single-line format
3730        original.split_whitespace().collect::<Vec<_>>().join(" ")
3731    }
3732
3733    /// Format an ExprMethodCall node as a string - extracts original source
3734    fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
3735        // Extract the original source code from the file content using the span
3736        let start = self.span_to_byte_offset(expr.span().start());
3737        let end = self.span_to_byte_offset(expr.span().end());
3738
3739        // Get the original text and collapse to single line
3740        let original = &self.content[start..end];
3741
3742        // Replace multiple whitespace/newlines with single space for single-line format
3743        original.split_whitespace().collect::<Vec<_>>().join(" ")
3744    }
3745
3746    /// Format an Ident node as a string - just return the identifier
3747    fn format_ident(&self, ident: &syn::Ident) -> String {
3748        ident.to_string()
3749    }
3750
3751    /// Format a TypePath node as a string - extracts original source
3752    fn format_type_path(&self, ty: &syn::TypePath) -> String {
3753        // Extract the original source code from the file content using the span
3754        let start = self.span_to_byte_offset(ty.span().start());
3755        let end = self.span_to_byte_offset(ty.span().end());
3756
3757        // Get the original text and collapse to single line
3758        let original = &self.content[start..end];
3759
3760        // Replace multiple whitespace/newlines with single space for single-line format
3761        original.split_whitespace().collect::<Vec<_>>().join(" ")
3762    }
3763
3764    /// Format an ExprMacro node as a string - extracts original source
3765    fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
3766        // Extract the original source code from the file content using the span
3767        let start = self.span_to_byte_offset(expr.span().start());
3768        let end = self.span_to_byte_offset(expr.span().end());
3769
3770        // Get the original text and collapse to single line
3771        let original = &self.content[start..end];
3772
3773        // Replace multiple whitespace/newlines with single space for single-line format
3774        original.split_whitespace().collect::<Vec<_>>().join(" ")
3775    }
3776
3777    /// Format a StmtMacro node as a string - extracts original source
3778    fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
3779        // Extract the original source code from the file content using the span
3780        let start = self.span_to_byte_offset(stmt.span().start());
3781        let end = self.span_to_byte_offset(stmt.span().end());
3782
3783        // Get the original text and collapse to single line
3784        let original = &self.content[start..end];
3785
3786        // Replace multiple whitespace/newlines with single space for single-line format
3787        original.split_whitespace().collect::<Vec<_>>().join(" ")
3788    }
3789
3790    /// Format an ItemStruct node as a string - extracts original source
3791    fn format_item_struct(&self, item: &syn::ItemStruct) -> String {
3792        let start = self.span_to_byte_offset(item.span().start());
3793        let end = self.span_to_byte_offset(item.span().end());
3794        let original = &self.content[start..end];
3795        original.to_string()
3796    }
3797
3798    /// Format an ItemEnum node as a string - extracts original source
3799    fn format_item_enum(&self, item: &syn::ItemEnum) -> String {
3800        let start = self.span_to_byte_offset(item.span().start());
3801        let end = self.span_to_byte_offset(item.span().end());
3802        let original = &self.content[start..end];
3803        original.to_string()
3804    }
3805
3806    /// Format an ItemEnum showing only a specific variant
3807    fn format_item_enum_variant_only(&self, item: &syn::ItemEnum, variant_name: &str) -> String {
3808        // Find the matching variant
3809        let variant = item.variants.iter()
3810            .find(|v| v.ident.to_string() == variant_name);
3811
3812        if let Some(variant) = variant {
3813            // Extract the enum header (visibility, enum keyword, name, generics, where clause)
3814            let enum_start = self.span_to_byte_offset(item.span().start());
3815            let variants_start = if !item.variants.is_empty() {
3816                self.span_to_byte_offset(item.variants.first().unwrap().span().start())
3817            } else {
3818                self.span_to_byte_offset(item.span().end())
3819            };
3820
3821            // Get everything before the first variant (header)
3822            let header = &self.content[enum_start..variants_start].trim_end();
3823
3824            // Extract the specific variant source
3825            let variant_start = self.span_to_byte_offset(variant.span().start());
3826            let variant_end = self.span_to_byte_offset(variant.span().end());
3827            let variant_source = &self.content[variant_start..variant_end];
3828
3829            // Build the filtered output
3830            format!("{}\n    {},\n    // ... {} other variant{}\n}}",
3831                header,
3832                variant_source,
3833                item.variants.len() - 1,
3834                if item.variants.len() - 1 == 1 { "" } else { "s" }
3835            )
3836        } else {
3837            // Fallback: show full enum if variant not found
3838            self.format_item_enum(item)
3839        }
3840    }
3841
3842    /// Format an ItemFn node as a string - extracts original source
3843    fn format_item_fn(&self, item: &syn::ItemFn) -> String {
3844        let start = self.span_to_byte_offset(item.span().start());
3845        let end = self.span_to_byte_offset(item.span().end());
3846        let original = &self.content[start..end];
3847        original.to_string()
3848    }
3849
3850    /// Format an ImplItemFn node as a string - extracts original source
3851    fn format_impl_item_fn(&self, item: &syn::ImplItemFn) -> String {
3852        let start = self.span_to_byte_offset(item.span().start());
3853        let end = self.span_to_byte_offset(item.span().end());
3854        let original = &self.content[start..end];
3855        original.to_string()
3856    }
3857
3858    /// Format a TraitItemFn node as a string - extracts original source
3859    fn format_trait_item_fn(&self, item: &syn::TraitItemFn) -> String {
3860        let start = self.span_to_byte_offset(item.span().start());
3861        let end = self.span_to_byte_offset(item.span().end());
3862        let original = &self.content[start..end];
3863        original.to_string()
3864    }
3865
3866    /// Format an ItemTrait node as a string - extracts original source
3867    fn format_item_trait(&self, item: &syn::ItemTrait) -> String {
3868        let start = self.span_to_byte_offset(item.span().start());
3869        let end = self.span_to_byte_offset(item.span().end());
3870        let original = &self.content[start..end];
3871        original.to_string()
3872    }
3873
3874    /// Format an ItemConst node as a string - extracts original source
3875    fn format_item_const(&self, item: &syn::ItemConst) -> String {
3876        let start = self.span_to_byte_offset(item.span().start());
3877        let end = self.span_to_byte_offset(item.span().end());
3878        let original = &self.content[start..end];
3879        original.to_string()
3880    }
3881
3882    /// Format an ItemStatic node as a string - extracts original source
3883    fn format_item_static(&self, item: &syn::ItemStatic) -> String {
3884        let start = self.span_to_byte_offset(item.span().start());
3885        let end = self.span_to_byte_offset(item.span().end());
3886        let original = &self.content[start..end];
3887        original.to_string()
3888    }
3889
3890    /// Format an ItemType node as a string - extracts original source
3891    fn format_item_type(&self, item: &syn::ItemType) -> String {
3892        let start = self.span_to_byte_offset(item.span().start());
3893        let end = self.span_to_byte_offset(item.span().end());
3894        let original = &self.content[start..end];
3895        original.to_string()
3896    }
3897
3898    /// Format an ItemMod node as a string - extracts original source
3899    fn format_item_mod(&self, item: &syn::ItemMod) -> String {
3900        let start = self.span_to_byte_offset(item.span().start());
3901        let end = self.span_to_byte_offset(item.span().end());
3902        let original = &self.content[start..end];
3903        original.to_string()
3904    }
3905
3906    /// Find the index of an item by type and name
3907    #[allow(dead_code)]
3908    pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
3909        for (index, item) in self.syntax_tree.items.iter().enumerate() {
3910            match (node_type, item) {
3911                ("struct", Item::Struct(s)) if s.ident == name => {
3912                    return Ok(index);
3913                }
3914                ("enum", Item::Enum(e)) if e.ident == name => {
3915                    return Ok(index);
3916                }
3917                ("fn", Item::Fn(f)) if f.sig.ident == name => {
3918                    return Ok(index);
3919                }
3920                ("impl", Item::Impl(impl_block)) => {
3921                    // For impl blocks, match on the self_ty
3922                    if let syn::Type::Path(type_path) = &*impl_block.self_ty {
3923                        if let Some(segment) = type_path.path.segments.last() {
3924                            if segment.ident == name {
3925                                return Ok(index);
3926                            }
3927                        }
3928                    }
3929                }
3930                _ => {}
3931            }
3932        }
3933
3934        anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
3935    }
3936
3937    /// Replace an item at a specific index with a new item
3938    #[allow(dead_code)]
3939    pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
3940        if index >= self.syntax_tree.items.len() {
3941            anyhow::bail!("Index {} out of bounds", index);
3942        }
3943
3944        // Replace the item in the syntax tree
3945        self.syntax_tree.items[index] = new_item;
3946
3947        // Reformat the entire file using prettyplease
3948        self.content = prettyplease::unparse(&self.syntax_tree);
3949
3950        // Recompute line offsets
3951        self.line_offsets = Self::compute_line_offsets(&self.content);
3952
3953        Ok(())
3954    }
3955
3956    fn span_to_location(&self, span: Span) -> NodeLocation {
3957        let start = span.start();
3958        let end = span.end();
3959
3960        NodeLocation {
3961            line: start.line,
3962            column: start.column,
3963            end_line: end.line,
3964            end_column: end.column,
3965        }
3966    }
3967
3968    /// Generic transform operation - find matching nodes and apply action
3969    pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
3970        use crate::operations::{InspectResult, TransformAction};
3971
3972        // First, use inspect to find all matching nodes (comments not needed for transform)
3973        let matches = self.inspect(Some(&op.node_type), op.name_filter.as_deref(), None, false)?;
3974
3975        // Apply content filter if specified
3976        let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
3977            matches.into_iter()
3978                .filter(|m| m.snippet.contains(content_filter))
3979                .collect()
3980        } else {
3981            matches
3982        };
3983
3984        if filtered_matches.is_empty() {
3985            return Ok(ModificationResult {
3986                changed: false,
3987                modified_nodes: vec![],
3988                unmatched_qualified_paths: None,
3989            });
3990        }
3991
3992        // Now apply the transformation action to each match
3993        // We need to work backwards through the file to avoid offset issues
3994        let mut sorted_matches = filtered_matches;
3995        sorted_matches.sort_by(|a, b| {
3996            b.location.line.cmp(&a.location.line)
3997                .then(b.location.column.cmp(&a.location.column))
3998        });
3999
4000        let mut modified_nodes = Vec::new();
4001
4002        for match_result in &sorted_matches {
4003            // Create backup node
4004            let backup_node = BackupNode {
4005                node_type: match_result.node_type.clone(),
4006                identifier: match_result.identifier.clone(),
4007                original_content: match_result.snippet.clone(),
4008                location: match_result.location.clone(),
4009            };
4010
4011            // Find the byte offsets for this node
4012            let start_offset = self.line_column_to_byte_offset(
4013                match_result.location.line,
4014                match_result.location.column
4015            )?;
4016            let end_offset = self.line_column_to_byte_offset(
4017                match_result.location.end_line,
4018                match_result.location.end_column
4019            )?;
4020
4021            // Extract the original text
4022            let original_text = &self.content[start_offset..end_offset];
4023
4024            // Apply the action
4025            let replacement = match &op.action {
4026                TransformAction::Comment => {
4027                    // Comment out the code
4028                    format!("// {}", original_text.replace("\n", "\n// "))
4029                }
4030                TransformAction::Remove => {
4031                    // Remove the entire node
4032                    String::new()
4033                }
4034                TransformAction::Replace { with } => {
4035                    // Replace with provided code
4036                    with.clone()
4037                }
4038            };
4039
4040            // Replace in content
4041            self.content.replace_range(start_offset..end_offset, &replacement);
4042
4043            // Recompute line offsets after each change
4044            self.line_offsets = Self::compute_line_offsets(&self.content);
4045
4046            modified_nodes.push(backup_node);
4047        }
4048
4049        // Re-parse the content if we made changes
4050        if !modified_nodes.is_empty() {
4051            // Don't reparse for now - we're doing text-level operations
4052            // self.syntax_tree = syn::parse_str(&self.content)
4053            //     .context("Failed to re-parse content after transformation")?;
4054        }
4055
4056        Ok(ModificationResult {
4057            changed: !modified_nodes.is_empty(),
4058            modified_nodes,
4059            unmatched_qualified_paths: None,
4060        })
4061    }
4062
4063    /// Rename an enum variant across the entire file
4064    pub(crate) fn rename_enum_variant(&mut self, op: &crate::operations::RenameEnumVariantOp) -> Result<ModificationResult> {
4065        use crate::operations::EditMode;
4066
4067        // Create a path resolver if a canonical path was provided
4068        let path_resolver = if let Some(enum_path) = &op.enum_path {
4069            let mut resolver = PathResolver::new(enum_path)
4070                .ok_or_else(|| anyhow::anyhow!("Invalid enum path: {}", enum_path))?;
4071
4072            // Scan the file for use statements to build the alias map
4073            resolver.scan_file(&self.syntax_tree);
4074            Some(resolver)
4075        } else {
4076            None
4077        };
4078
4079        match op.edit_mode {
4080            EditMode::Surgical => {
4081                // Use non-mutating visitor to collect replacement locations
4082                use syn::visit::Visit;
4083                use crate::surgical::Replacement;
4084
4085                let mut collector = EnumVariantReplacementCollector {
4086                    enum_name: op.enum_name.clone(),
4087                    old_variant: op.old_variant.clone(),
4088                    new_variant: op.new_variant.clone(),
4089                    path_resolver,
4090                    replacements: Vec::new(),
4091                };
4092
4093                collector.visit_file(&self.syntax_tree);
4094
4095                if collector.replacements.is_empty() {
4096                    return Ok(ModificationResult {
4097                        changed: false,
4098                        modified_nodes: vec![],
4099            unmatched_qualified_paths: None,
4100                    });
4101                }
4102
4103                // Apply surgical edits to original content
4104                self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
4105
4106                // Recompute line offsets
4107                self.line_offsets = Self::compute_line_offsets(&self.content);
4108
4109                // Re-parse the modified content
4110                self.syntax_tree = syn::parse_str(&self.content)
4111                    .context("Failed to re-parse after surgical edit")?;
4112
4113                let backup_node = BackupNode {
4114                    node_type: "EnumVariantRename".to_string(),
4115                    identifier: format!("{}::{} -> {} (surgical)", op.enum_name, op.old_variant, op.new_variant),
4116                    original_content: format!("Renamed {} to {} in enum {} (surgical mode)", op.old_variant, op.new_variant, op.enum_name),
4117                    location: NodeLocation {
4118                        line: 1,
4119                        column: 0,
4120                        end_line: 1,
4121                        end_column: 0,
4122                    },
4123                };
4124
4125                Ok(ModificationResult {
4126                    changed: true,
4127                    modified_nodes: vec![backup_node],
4128            unmatched_qualified_paths: None,
4129                })
4130            }
4131            EditMode::Reformat => {
4132                // Use mutating visitor (original behavior)
4133                let mut renamer = EnumVariantRenamer {
4134                    enum_name: op.enum_name.clone(),
4135                    old_variant: op.old_variant.clone(),
4136                    new_variant: op.new_variant.clone(),
4137                    path_resolver,
4138                    modified: false,
4139                };
4140
4141                // Visit and mutate the syntax tree
4142                renamer.visit_file_mut(&mut self.syntax_tree);
4143
4144                if !renamer.modified {
4145                    return Ok(ModificationResult {
4146                        changed: false,
4147                        modified_nodes: vec![],
4148            unmatched_qualified_paths: None,
4149                    });
4150                }
4151
4152                // Reformat the entire file using prettyplease
4153                self.content = prettyplease::unparse(&self.syntax_tree);
4154
4155                // Recompute line offsets
4156                self.line_offsets = Self::compute_line_offsets(&self.content);
4157
4158                // Create a backup node for the entire file operation
4159                let backup_node = BackupNode {
4160                    node_type: "EnumVariantRename".to_string(),
4161                    identifier: format!("{}::{} -> {}", op.enum_name, op.old_variant, op.new_variant),
4162                    original_content: format!("Renamed {} to {} in enum {}", op.old_variant, op.new_variant, op.enum_name),
4163                    location: NodeLocation {
4164                        line: 1,
4165                        column: 0,
4166                        end_line: 1,
4167                        end_column: 0,
4168                    },
4169                };
4170
4171                Ok(ModificationResult {
4172                    changed: true,
4173                    modified_nodes: vec![backup_node],
4174            unmatched_qualified_paths: None,
4175                })
4176            }
4177        }
4178    }
4179
4180    /// Rename a function across the entire file
4181    pub(crate) fn rename_function(&mut self, op: &crate::operations::RenameFunctionOp) -> Result<ModificationResult> {
4182        use crate::operations::EditMode;
4183
4184        // Create a path resolver if a canonical path was provided
4185        let path_resolver = if let Some(function_path) = &op.function_path {
4186            let mut resolver = PathResolver::new(function_path)
4187                .ok_or_else(|| anyhow::anyhow!("Invalid function path: {}", function_path))?;
4188
4189            // Scan the file for use statements to build the alias map
4190            resolver.scan_file(&self.syntax_tree);
4191            Some(resolver)
4192        } else {
4193            None
4194        };
4195
4196        match op.edit_mode {
4197            EditMode::Surgical => {
4198                // Use non-mutating visitor to collect replacement locations
4199                use syn::visit::Visit;
4200
4201                let mut collector = FunctionReplacementCollector {
4202                    old_name: op.old_name.clone(),
4203                    new_name: op.new_name.clone(),
4204                    path_resolver,
4205                    replacements: Vec::new(),
4206                };
4207
4208                collector.visit_file(&self.syntax_tree);
4209
4210                if collector.replacements.is_empty() {
4211                    return Ok(ModificationResult {
4212                        changed: false,
4213                        modified_nodes: vec![],
4214            unmatched_qualified_paths: None,
4215                    });
4216                }
4217
4218                // Apply surgical edits to original content
4219                self.content = crate::surgical::apply_surgical_edits(&self.content, collector.replacements);
4220
4221                // Recompute line offsets
4222                self.line_offsets = Self::compute_line_offsets(&self.content);
4223
4224                // Re-parse the modified content
4225                self.syntax_tree = syn::parse_str(&self.content)
4226                    .context("Failed to re-parse after surgical edit")?;
4227
4228                let backup_node = BackupNode {
4229                    node_type: "FunctionRename".to_string(),
4230                    identifier: format!("{} -> {} (surgical)", op.old_name, op.new_name),
4231                    original_content: format!("Renamed {} to {} (surgical mode)", op.old_name, op.new_name),
4232                    location: NodeLocation {
4233                        line: 1,
4234                        column: 0,
4235                        end_line: 1,
4236                        end_column: 0,
4237                    },
4238                };
4239
4240                Ok(ModificationResult {
4241                    changed: true,
4242                    modified_nodes: vec![backup_node],
4243            unmatched_qualified_paths: None,
4244                })
4245            }
4246            EditMode::Reformat => {
4247                // Use mutating visitor (original behavior)
4248                let mut renamer = FunctionRenamer {
4249                    old_name: op.old_name.clone(),
4250                    new_name: op.new_name.clone(),
4251                    path_resolver,
4252                    modified: false,
4253                };
4254
4255                // Visit and mutate the syntax tree
4256                renamer.visit_file_mut(&mut self.syntax_tree);
4257
4258                if !renamer.modified {
4259                    return Ok(ModificationResult {
4260                        changed: false,
4261                        modified_nodes: vec![],
4262            unmatched_qualified_paths: None,
4263                    });
4264                }
4265
4266                // Reformat the entire file using prettyplease
4267                self.content = prettyplease::unparse(&self.syntax_tree);
4268
4269                // Recompute line offsets
4270                self.line_offsets = Self::compute_line_offsets(&self.content);
4271
4272                // Create a backup node for the entire file operation
4273                let backup_node = BackupNode {
4274                    node_type: "FunctionRename".to_string(),
4275                    identifier: format!("{} -> {}", op.old_name, op.new_name),
4276                    original_content: format!("Renamed {} to {}", op.old_name, op.new_name),
4277                    location: NodeLocation {
4278                        line: 1,
4279                        column: 0,
4280                        end_line: 1,
4281                        end_column: 0,
4282                    },
4283                };
4284
4285                Ok(ModificationResult {
4286                    changed: true,
4287                    modified_nodes: vec![backup_node],
4288            unmatched_qualified_paths: None,
4289                })
4290            }
4291        }
4292    }
4293
4294    /// Convert line/column to byte offset
4295    fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
4296        if line == 0 || line > self.line_offsets.len() {
4297            anyhow::bail!("Line {} out of range", line);
4298        }
4299
4300        let line_start = self.line_offsets[line - 1];
4301        Ok(line_start + column)
4302    }
4303}
4304
4305// Visitor for adding match arms
4306struct MatchArmAdder {
4307    target_function: Option<String>,
4308    arm_to_add: Arm,
4309    modified: bool,
4310    current_function: Option<String>,
4311    modified_function: Option<String>,
4312}
4313
4314impl VisitMut for MatchArmAdder {
4315    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4316        let prev_fn = self.current_function.clone();
4317        self.current_function = Some(node.sig.ident.to_string());
4318
4319        // Continue visiting nested items
4320        syn::visit_mut::visit_item_fn_mut(self, node);
4321
4322        self.current_function = prev_fn;
4323    }
4324
4325    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4326        // Check if we're in the right function (if specified)
4327        if let Some(ref target) = self.target_function {
4328            if self.current_function.as_ref() != Some(target) {
4329                // Continue visiting nested expressions
4330                syn::visit_mut::visit_expr_match_mut(self, node);
4331                return;
4332            }
4333        }
4334
4335        // Check if the pattern already exists (idempotent)
4336        let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
4337        let already_exists = node.arms.iter().any(|arm| {
4338            arm.pat.to_token_stream().to_string() == pattern_str
4339        });
4340
4341        if !already_exists {
4342            // Add the arm to the end
4343            node.arms.push(self.arm_to_add.clone());
4344            self.modified = true;
4345            self.modified_function = self.current_function.clone();
4346        }
4347
4348        // Continue visiting nested expressions
4349        syn::visit_mut::visit_expr_match_mut(self, node);
4350    }
4351}
4352
4353// Visitor for updating match arms
4354struct MatchArmUpdater {
4355    target_function: Option<String>,
4356    pattern_to_match: String,
4357    new_body: syn::Expr,
4358    modified: bool,
4359    current_function: Option<String>,
4360    modified_function: Option<String>,
4361}
4362
4363impl VisitMut for MatchArmUpdater {
4364    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4365        let prev_fn = self.current_function.clone();
4366        self.current_function = Some(node.sig.ident.to_string());
4367
4368        syn::visit_mut::visit_item_fn_mut(self, node);
4369
4370        self.current_function = prev_fn;
4371    }
4372
4373    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4374        // Check if we're in the right function (if specified)
4375        if let Some(ref target) = self.target_function {
4376            if self.current_function.as_ref() != Some(target) {
4377                syn::visit_mut::visit_expr_match_mut(self, node);
4378                return;
4379            }
4380        }
4381
4382        // Find and update the matching arm
4383        for arm in &mut node.arms {
4384            let pattern_str = arm.pat.to_token_stream().to_string();
4385            // Normalize whitespace for comparison
4386            let pattern_normalized = pattern_str.replace(" ", "");
4387            let target_normalized = self.pattern_to_match.replace(" ", "");
4388
4389            if pattern_normalized == target_normalized {
4390                arm.body = Box::new(self.new_body.clone());
4391                self.modified = true;
4392                self.modified_function = self.current_function.clone();
4393                break;
4394            }
4395        }
4396
4397        syn::visit_mut::visit_expr_match_mut(self, node);
4398    }
4399}
4400
4401// Visitor for removing match arms
4402struct MatchArmRemover {
4403    target_function: Option<String>,
4404    pattern_to_remove: String,
4405    modified: bool,
4406    current_function: Option<String>,
4407    modified_function: Option<String>,
4408}
4409
4410impl VisitMut for MatchArmRemover {
4411    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4412        let prev_fn = self.current_function.clone();
4413        self.current_function = Some(node.sig.ident.to_string());
4414
4415        syn::visit_mut::visit_item_fn_mut(self, node);
4416
4417        self.current_function = prev_fn;
4418    }
4419
4420    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4421        // Check if we're in the right function (if specified)
4422        if let Some(ref target) = self.target_function {
4423            if self.current_function.as_ref() != Some(target) {
4424                syn::visit_mut::visit_expr_match_mut(self, node);
4425                return;
4426            }
4427        }
4428
4429        // Find and remove the matching arm
4430        let mut index_to_remove = None;
4431        for (i, arm) in node.arms.iter().enumerate() {
4432            let pattern_str = arm.pat.to_token_stream().to_string();
4433            // Normalize whitespace for comparison
4434            let pattern_normalized = pattern_str.replace(" ", "");
4435            let target_normalized = self.pattern_to_remove.replace(" ", "");
4436
4437            if pattern_normalized == target_normalized {
4438                index_to_remove = Some(i);
4439                break;
4440            }
4441        }
4442
4443        if let Some(index) = index_to_remove {
4444            node.arms.remove(index);
4445            self.modified = true;
4446            self.modified_function = self.current_function.clone();
4447        }
4448
4449        syn::visit_mut::visit_expr_match_mut(self, node);
4450    }
4451}
4452
4453// Visitor for adding multiple match arms at once (for auto-detect)
4454struct MultiMatchArmAdder {
4455    target_function: Option<String>,
4456    arms_to_add: Vec<(String, Arm)>,  // (pattern_string, arm)
4457    modified: bool,
4458    current_function: Option<String>,
4459    modified_function: Option<String>,
4460}
4461
4462impl VisitMut for MultiMatchArmAdder {
4463    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4464        let prev_fn = self.current_function.clone();
4465        self.current_function = Some(node.sig.ident.to_string());
4466
4467        syn::visit_mut::visit_item_fn_mut(self, node);
4468
4469        self.current_function = prev_fn;
4470    }
4471
4472    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
4473        // Check if we're in the right function (if specified)
4474        if let Some(ref target) = self.target_function {
4475            if self.current_function.as_ref() != Some(target) {
4476                syn::visit_mut::visit_expr_match_mut(self, node);
4477                return;
4478            }
4479        }
4480
4481        // Add all missing arms
4482        for (pattern_str, arm) in &self.arms_to_add {
4483            // Check if the pattern already exists (idempotent)
4484            let already_exists = node.arms.iter().any(|existing_arm| {
4485                existing_arm.pat.to_token_stream().to_string() == *pattern_str
4486            });
4487
4488            if !already_exists {
4489                node.arms.push(arm.clone());
4490                self.modified = true;
4491                self.modified_function = self.current_function.clone();
4492            }
4493        }
4494
4495        syn::visit_mut::visit_expr_match_mut(self, node);
4496    }
4497}
4498
4499// Visitor for adding fields to struct literal expressions
4500struct StructLiteralFieldAdder {
4501    struct_name: String,
4502    field_def: String,
4503    field_name: String,
4504    position: InsertPosition,
4505    path_resolver: Option<PathResolver>,
4506    modified: bool,
4507}
4508
4509impl VisitMut for StructLiteralFieldAdder {
4510    fn visit_expr_mut(&mut self, node: &mut Expr) {
4511        // Check if this is a struct literal expression
4512        if let Expr::Struct(expr_struct) = node {
4513            let is_match = if let Some(resolver) = &self.path_resolver {
4514                // Use PathResolver for safe matching
4515                resolver.matches_target(&expr_struct.path)
4516            } else {
4517                // Fallback to legacy pattern matching
4518                // - "Rectangle" → only Rectangle { ... } (no :: prefix)
4519                // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
4520                // - "View::Rectangle" → exact match only View::Rectangle
4521
4522                if self.struct_name.contains("::") {
4523                    // Pattern contains :: - check for exact or wildcard match
4524                    if self.struct_name.starts_with("*::") {
4525                        // Wildcard: *::Rectangle matches any path ending with Rectangle
4526                        let target_name = &self.struct_name[3..]; // Skip "*::"
4527                        expr_struct.path.segments.last()
4528                            .map(|seg| seg.ident.to_string() == target_name)
4529                            .unwrap_or(false)
4530                    } else {
4531                        // Exact path match: View::Rectangle
4532                        let path_str = expr_struct.path.segments.iter()
4533                            .map(|seg| seg.ident.to_string())
4534                            .collect::<Vec<_>>()
4535                            .join("::");
4536                        path_str == self.struct_name
4537                    }
4538                } else {
4539                    // No :: in pattern - only match pure struct literals (no path qualifier)
4540                    expr_struct.path.segments.len() == 1
4541                        && expr_struct.path.segments.last()
4542                            .map(|seg| seg.ident.to_string())
4543                            .as_ref() == Some(&self.struct_name)
4544                }
4545            };
4546
4547            if is_match {
4548                // Check if field already exists (idempotent)
4549                let field_exists = expr_struct.fields.iter().any(|fv| {
4550                    fv.member.to_token_stream().to_string() == self.field_name
4551                });
4552
4553                if !field_exists {
4554                    // Parse the field value from field_def
4555                    // field_def is like "return_type: None"
4556                    let field_value_code = format!("{{ {} }}", self.field_def);
4557                    if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
4558                        if let Some(new_fv) = expr.fields.first() {
4559                            // Determine where to insert
4560                            match &self.position {
4561                                InsertPosition::First => {
4562                                    expr_struct.fields.insert(0, new_fv.clone());
4563                                    self.modified = true;
4564                                }
4565                                InsertPosition::Last => {
4566                                    expr_struct.fields.push(new_fv.clone());
4567                                    self.modified = true;
4568                                }
4569                                InsertPosition::After(after_field) => {
4570                                    // Find the position of the field to insert after
4571                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
4572                                        fv.member.to_token_stream().to_string() == *after_field
4573                                    }) {
4574                                        expr_struct.fields.insert(pos + 1, new_fv.clone());
4575                                        self.modified = true;
4576                                    }
4577                                }
4578                                InsertPosition::Before(before_field) => {
4579                                    // Find the position of the field to insert before
4580                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
4581                                        fv.member.to_token_stream().to_string() == *before_field
4582                                    }) {
4583                                        expr_struct.fields.insert(pos, new_fv.clone());
4584                                        self.modified = true;
4585                                    }
4586                                }
4587                            }
4588                        }
4589                    }
4590                }
4591            }
4592        }
4593
4594        // IMPORTANT: Visit children AFTER processing this node
4595        // This ensures we traverse into nested expressions
4596        syn::visit_mut::visit_expr_mut(self, node);
4597    }
4598}
4599
4600// Visitor for renaming enum variants
4601struct EnumVariantRenamer {
4602    enum_name: String,
4603    old_variant: String,
4604    new_variant: String,
4605    path_resolver: Option<PathResolver>,
4606    modified: bool,
4607}
4608
4609impl EnumVariantRenamer {
4610    /// Rename a path segment if it matches using path resolution.
4611    ///
4612    /// This method handles various path forms:
4613    /// - Simple paths: `EnumName::Variant`
4614    /// - Qualified paths: `crate::module::EnumName::Variant`
4615    /// - Imported paths: `Variant` (when enum is imported via use statement)
4616    ///
4617    /// When a PathResolver is configured, it validates that paths refer to
4618    /// the correct enum before renaming.
4619    fn rename_path(&mut self, path: &mut syn::Path) {
4620        // Check if the path ends with EnumName::VariantName
4621        let segments: Vec<_> = path.segments.iter().collect();
4622        let len = segments.len();
4623
4624        if len >= 2 {
4625            // Path has at least enum and variant segments
4626            let potential_variant = &segments[len - 1];
4627            let potential_enum = &segments[len - 2];
4628
4629            if potential_enum.ident == self.enum_name
4630                && potential_variant.ident == self.old_variant
4631            {
4632                // Path ends with EnumName::VariantName
4633
4634                // If we have a path resolver, validate the enum path
4635                if let Some(resolver) = &self.path_resolver {
4636                    // Extract just the enum path (everything except the variant)
4637                    let enum_path = syn::Path {
4638                        leading_colon: path.leading_colon,
4639                        segments: path.segments.iter()
4640                            .take(len - 1)
4641                            .cloned()
4642                            .collect(),
4643                    };
4644
4645                    // Only rename if the enum path matches our target
4646                    if resolver.matches_target(&enum_path) {
4647                        path.segments[len - 1].ident = syn::Ident::new(
4648                            &self.new_variant,
4649                            path.segments[len - 1].ident.span()
4650                        );
4651                        self.modified = true;
4652                    }
4653                } else {
4654                    // No resolver - use simple matching (backward compatible)
4655                    // Only match if it's exactly EnumName::Variant (2 segments)
4656                    if len == 2 {
4657                        path.segments[1].ident = syn::Ident::new(
4658                            &self.new_variant,
4659                            path.segments[1].ident.span()
4660                        );
4661                        self.modified = true;
4662                    }
4663                }
4664            }
4665        } else if len == 1 {
4666            // Single segment path - check if it's an imported variant
4667            if segments[0].ident == self.old_variant {
4668                // When using path resolver, we don't rename single-segment paths
4669                // unless they're explicitly imported (which would be unusual for variants)
4670                // For backward compatibility, we still rename them when no resolver is present
4671                if self.path_resolver.is_none() {
4672                    path.segments[0].ident = syn::Ident::new(
4673                        &self.new_variant,
4674                        path.segments[0].ident.span()
4675                    );
4676                    self.modified = true;
4677                }
4678            }
4679        }
4680    }
4681}
4682
4683impl VisitMut for EnumVariantRenamer {
4684    /// Rename in enum definition
4685    fn visit_item_enum_mut(&mut self, node: &mut syn::ItemEnum) {
4686        if node.ident == self.enum_name {
4687            for variant in &mut node.variants {
4688                if variant.ident == self.old_variant {
4689                    variant.ident = syn::Ident::new(&self.new_variant, variant.ident.span());
4690                    self.modified = true;
4691                }
4692            }
4693        }
4694
4695        // Continue visiting nested items
4696        syn::visit_mut::visit_item_enum_mut(self, node);
4697    }
4698
4699    /// Rename in patterns (match arms, let bindings, function parameters, etc.)
4700    fn visit_pat_mut(&mut self, pat: &mut syn::Pat) {
4701        match pat {
4702            syn::Pat::TupleStruct(tuple_struct) => {
4703                self.rename_path(&mut tuple_struct.path);
4704            }
4705            syn::Pat::Struct(struct_pat) => {
4706                self.rename_path(&mut struct_pat.path);
4707            }
4708            syn::Pat::Path(path_pat) => {
4709                self.rename_path(&mut path_pat.path);
4710            }
4711            _ => {}
4712        }
4713
4714        // Continue visiting nested patterns
4715        syn::visit_mut::visit_pat_mut(self, pat);
4716    }
4717
4718    /// Rename in expressions (constructor calls, references, etc.)
4719    fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
4720        match expr {
4721            syn::Expr::Path(expr_path) => {
4722                self.rename_path(&mut expr_path.path);
4723            }
4724            syn::Expr::Call(call) => {
4725                if let syn::Expr::Path(path) = &mut *call.func {
4726                    self.rename_path(&mut path.path);
4727                }
4728            }
4729            syn::Expr::Struct(struct_expr) => {
4730                self.rename_path(&mut struct_expr.path);
4731            }
4732            _ => {}
4733        }
4734
4735        // Continue visiting nested expressions
4736        syn::visit_mut::visit_expr_mut(self, expr);
4737    }
4738}
4739
4740// Non-mutating visitor for collecting replacement locations (surgical mode)
4741struct EnumVariantReplacementCollector {
4742    enum_name: String,
4743    old_variant: String,
4744    new_variant: String,
4745    path_resolver: Option<PathResolver>,
4746    replacements: Vec<crate::surgical::Replacement>,
4747}
4748
4749impl EnumVariantReplacementCollector {
4750    /// Check if a path matches and collect replacement if it does
4751    fn collect_path_replacement(&mut self, path: &syn::Path) {
4752        let segments: Vec<_> = path.segments.iter().collect();
4753        let len = segments.len();
4754
4755        if len >= 2 {
4756            let potential_variant = &segments[len - 1];
4757            let potential_enum = &segments[len - 2];
4758
4759            if potential_enum.ident == self.enum_name
4760                && potential_variant.ident == self.old_variant
4761            {
4762                // Path ends with EnumName::VariantName
4763
4764                // Validate with path resolver if available
4765                let should_rename = if let Some(resolver) = &self.path_resolver {
4766                    let enum_path = syn::Path {
4767                        leading_colon: path.leading_colon,
4768                        segments: path.segments.iter()
4769                            .take(len - 1)
4770                            .cloned()
4771                            .collect(),
4772                    };
4773                    resolver.matches_target(&enum_path)
4774                } else {
4775                    // No resolver - only match exactly 2 segments
4776                    len == 2
4777                };
4778
4779                if should_rename {
4780                    let span = potential_variant.ident.span();
4781                    let start = span.start();
4782                    let end = span.end();
4783
4784                    self.replacements.push(crate::surgical::Replacement::new(
4785                        start,
4786                        end,
4787                        self.new_variant.clone(),
4788                    ));
4789                }
4790            }
4791        } else if len == 1 && self.path_resolver.is_none() {
4792            // Single segment - only without path resolver (backward compat)
4793            if segments[0].ident == self.old_variant {
4794                let span = segments[0].ident.span();
4795                let start = span.start();
4796                let end = span.end();
4797
4798                self.replacements.push(crate::surgical::Replacement::new(
4799                    start,
4800                    end,
4801                    self.new_variant.clone(),
4802                ));
4803            }
4804        }
4805    }
4806}
4807
4808impl<'ast> syn::visit::Visit<'ast> for EnumVariantReplacementCollector {
4809    fn visit_item_enum(&mut self, node: &'ast syn::ItemEnum) {
4810        if node.ident == self.enum_name {
4811            for variant in &node.variants {
4812                if variant.ident == self.old_variant {
4813                    let span = variant.ident.span();
4814                    let start = span.start();
4815                    let end = span.end();
4816
4817                    self.replacements.push(crate::surgical::Replacement::new(
4818                        start,
4819                        end,
4820                        self.new_variant.clone(),
4821                    ));
4822                }
4823            }
4824        }
4825        syn::visit::visit_item_enum(self, node);
4826    }
4827
4828    fn visit_pat(&mut self, pat: &'ast syn::Pat) {
4829        match pat {
4830            syn::Pat::TupleStruct(tuple_struct) => {
4831                self.collect_path_replacement(&tuple_struct.path);
4832            }
4833            syn::Pat::Struct(struct_pat) => {
4834                self.collect_path_replacement(&struct_pat.path);
4835            }
4836            syn::Pat::Path(path_pat) => {
4837                self.collect_path_replacement(&path_pat.path);
4838            }
4839            _ => {}
4840        }
4841        syn::visit::visit_pat(self, pat);
4842    }
4843
4844    fn visit_expr(&mut self, expr: &'ast syn::Expr) {
4845        match expr {
4846            syn::Expr::Path(expr_path) => {
4847                self.collect_path_replacement(&expr_path.path);
4848            }
4849            syn::Expr::Call(call) => {
4850                if let syn::Expr::Path(path) = &*call.func {
4851                    self.collect_path_replacement(&path.path);
4852                }
4853            }
4854            syn::Expr::Struct(struct_expr) => {
4855                self.collect_path_replacement(&struct_expr.path);
4856            }
4857            _ => {}
4858        }
4859        syn::visit::visit_expr(self, expr);
4860    }
4861}
4862
4863// Mutating visitor for renaming functions (reformat mode)
4864struct FunctionRenamer {
4865    old_name: String,
4866    new_name: String,
4867    path_resolver: Option<PathResolver>,
4868    modified: bool,
4869}
4870
4871impl FunctionRenamer {
4872    /// Rename a function identifier if it matches
4873    fn rename_ident(&mut self, ident: &mut syn::Ident) {
4874        if ident == &self.old_name {
4875            *ident = syn::Ident::new(&self.new_name, ident.span());
4876            self.modified = true;
4877        }
4878    }
4879
4880    /// Check if a path matches our target function (with path resolution)
4881    fn matches_target_function(&self, path: &syn::Path) -> bool {
4882        if let Some(resolver) = &self.path_resolver {
4883            resolver.matches_target(path)
4884        } else {
4885            // Simple matching: just check if the last segment is our function name
4886            path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
4887        }
4888    }
4889}
4890
4891impl VisitMut for FunctionRenamer {
4892    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
4893        // Rename function definition
4894        self.rename_ident(&mut node.sig.ident);
4895        syn::visit_mut::visit_item_fn_mut(self, node);
4896    }
4897
4898    fn visit_impl_item_fn_mut(&mut self, node: &mut syn::ImplItemFn) {
4899        // Rename impl method definition
4900        self.rename_ident(&mut node.sig.ident);
4901        syn::visit_mut::visit_impl_item_fn_mut(self, node);
4902    }
4903
4904    fn visit_trait_item_fn_mut(&mut self, node: &mut syn::TraitItemFn) {
4905        // Rename trait method definition
4906        self.rename_ident(&mut node.sig.ident);
4907        syn::visit_mut::visit_trait_item_fn_mut(self, node);
4908    }
4909
4910    fn visit_expr_mut(&mut self, expr: &mut syn::Expr) {
4911        match expr {
4912            syn::Expr::Call(call) => {
4913                // Rename function calls
4914                if let syn::Expr::Path(expr_path) = &mut *call.func {
4915                    if self.matches_target_function(&expr_path.path) {
4916                        if let Some(last_seg) = expr_path.path.segments.last_mut() {
4917                            self.rename_ident(&mut last_seg.ident);
4918                        }
4919                    }
4920                }
4921            }
4922            syn::Expr::Path(expr_path) => {
4923                // Rename function references (not calls)
4924                if self.matches_target_function(&expr_path.path) {
4925                    if let Some(last_seg) = expr_path.path.segments.last_mut() {
4926                        self.rename_ident(&mut last_seg.ident);
4927                    }
4928                }
4929            }
4930            _ => {}
4931        }
4932        syn::visit_mut::visit_expr_mut(self, expr);
4933    }
4934}
4935
4936// Non-mutating visitor for collecting function replacement locations (surgical mode)
4937struct FunctionReplacementCollector {
4938    old_name: String,
4939    new_name: String,
4940    path_resolver: Option<PathResolver>,
4941    replacements: Vec<crate::surgical::Replacement>,
4942}
4943
4944impl FunctionReplacementCollector {
4945    /// Collect replacement for a function identifier
4946    fn collect_replacement(&mut self, ident: &syn::Ident) {
4947        if ident == &self.old_name {
4948            let span = ident.span();
4949            let start = span.start();
4950            let end = span.end();
4951
4952            self.replacements.push(crate::surgical::Replacement::new(
4953                start,
4954                end,
4955                self.new_name.clone(),
4956            ));
4957        }
4958    }
4959
4960    /// Check if a path matches our target function
4961    fn matches_target_function(&self, path: &syn::Path) -> bool {
4962        if let Some(resolver) = &self.path_resolver {
4963            resolver.matches_target(path)
4964        } else {
4965            // Simple matching
4966            path.segments.len() == 1 && path.segments.last().unwrap().ident == self.old_name
4967        }
4968    }
4969}
4970
4971impl<'ast> syn::visit::Visit<'ast> for FunctionReplacementCollector {
4972    fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
4973        // Collect function definition rename
4974        self.collect_replacement(&node.sig.ident);
4975        syn::visit::visit_item_fn(self, node);
4976    }
4977
4978    fn visit_impl_item_fn(&mut self, node: &'ast syn::ImplItemFn) {
4979        // Collect impl method definition rename
4980        self.collect_replacement(&node.sig.ident);
4981        syn::visit::visit_impl_item_fn(self, node);
4982    }
4983
4984    fn visit_trait_item_fn(&mut self, node: &'ast syn::TraitItemFn) {
4985        // Collect trait method definition rename
4986        self.collect_replacement(&node.sig.ident);
4987        syn::visit::visit_trait_item_fn(self, node);
4988    }
4989
4990    fn visit_expr(&mut self, expr: &'ast syn::Expr) {
4991        match expr {
4992            syn::Expr::Call(call) => {
4993                // Collect function call renames
4994                if let syn::Expr::Path(expr_path) = &*call.func {
4995                    if self.matches_target_function(&expr_path.path) {
4996                        if let Some(last_seg) = expr_path.path.segments.last() {
4997                            self.collect_replacement(&last_seg.ident);
4998                        }
4999                    }
5000                }
5001                // Visit call arguments but NOT the func (already handled above)
5002                for arg in &call.args {
5003                    syn::visit::visit_expr(self, arg);
5004                }
5005                // Don't call the default visitor which would re-visit call.func
5006                return;
5007            }
5008            syn::Expr::Path(expr_path) => {
5009                // Collect function reference renames (not calls)
5010                if self.matches_target_function(&expr_path.path) {
5011                    if let Some(last_seg) = expr_path.path.segments.last() {
5012                        self.collect_replacement(&last_seg.ident);
5013                    }
5014                }
5015            }
5016            _ => {}
5017        }
5018        syn::visit::visit_expr(self, expr);
5019    }
5020}
5021
5022// ============================================================================
5023// Doc Comment Operations
5024// ============================================================================
5025
5026/// Generate a documentation comment in the specified style
5027fn generate_doc_comment(text: &str, style: &DocCommentStyle) -> String {
5028    match style {
5029        DocCommentStyle::Line => {
5030            // Split by newlines and add /// prefix to each line
5031            text.lines()
5032                .map(|line| {
5033                    if line.trim().is_empty() {
5034                        "///".to_string()
5035                    } else {
5036                        format!("/// {}", line)
5037                    }
5038                })
5039                .collect::<Vec<_>>()
5040                .join("\n")
5041        }
5042        DocCommentStyle::Block => {
5043            // Simple block comment
5044            if text.contains('\n') {
5045                // Multi-line block comment
5046                let lines = text.lines()
5047                    .map(|line| format!(" * {}", line))
5048                    .collect::<Vec<_>>()
5049                    .join("\n");
5050                format!("/**\n{}\n */", lines)
5051            } else {
5052                // Single-line block comment
5053                format!("/** {} */", text)
5054            }
5055        }
5056    }
5057}
5058
5059/// Extract preceding comments (both doc and regular) before a given line
5060/// Returns None if no comments found, Some(comment_text) if comments exist
5061fn extract_preceding_comment(content: &str, start_line: usize) -> Option<String> {
5062    if start_line == 0 {
5063        return None;
5064    }
5065
5066    let lines: Vec<&str> = content.lines().collect();
5067    if start_line > lines.len() {
5068        return None;
5069    }
5070
5071    let line_idx = start_line.saturating_sub(1); // Convert 1-based to 0-based
5072
5073    // Scan backwards from target to find comment lines
5074    let mut comment_start = line_idx;
5075    let mut found_any_comment = false;
5076
5077    while comment_start > 0 {
5078        let prev_line = lines[comment_start - 1].trim();
5079
5080        // Check if this is a comment line
5081        let is_comment = prev_line.starts_with("///")
5082            || prev_line.starts_with("//!")
5083            || prev_line.starts_with("//")
5084            || prev_line.starts_with("/**")
5085            || prev_line.starts_with("/*!")
5086            || prev_line.starts_with("/*")
5087            || (prev_line.starts_with("*") && !prev_line.starts_with("*/"))
5088            || prev_line == "*/";
5089
5090        if is_comment {
5091            comment_start -= 1;
5092            found_any_comment = true;
5093        } else if prev_line.is_empty() && found_any_comment {
5094            // Allow blank lines within comment blocks, but don't continue past them
5095            // unless we're in the middle of a block comment
5096            comment_start -= 1;
5097        } else {
5098            break;
5099        }
5100    }
5101
5102    if !found_any_comment {
5103        return None;
5104    }
5105
5106    // Extract the comment lines and preserve original formatting
5107    let comment_lines: Vec<String> = lines[comment_start..line_idx]
5108        .iter()
5109        .map(|&line| line.to_string())
5110        .collect();
5111
5112    if comment_lines.is_empty() {
5113        None
5114    } else {
5115        Some(comment_lines.join("\n"))
5116    }
5117}
5118
5119/// Find the byte position and indentation of a target item
5120struct TargetFinder {
5121    target_type: String,
5122    target_name: String,
5123    found_position: Option<(usize, String)>, // (line_number, indentation)
5124}
5125
5126impl TargetFinder {
5127    fn new(target_type: String, target_name: String) -> Self {
5128        Self {
5129            target_type,
5130            target_name,
5131            found_position: None,
5132        }
5133    }
5134}
5135
5136impl<'ast> syn::visit::Visit<'ast> for TargetFinder {
5137    fn visit_item_struct(&mut self, node: &'ast ItemStruct) {
5138        if self.target_type == "struct" && node.ident.to_string() == self.target_name {
5139            // Use struct_token span to get the line where "struct" keyword appears,
5140            // not the first line of the item (which includes doc comments)
5141            let line = node.struct_token.span.start().line;
5142            self.found_position = Some((line, String::new()));
5143        }
5144        syn::visit::visit_item_struct(self, node);
5145    }
5146
5147    fn visit_item_enum(&mut self, node: &'ast ItemEnum) {
5148        if self.target_type == "enum" && node.ident.to_string() == self.target_name {
5149            // Use enum_token span to get the line where "enum" keyword appears
5150            let line = node.enum_token.span.start().line;
5151            self.found_position = Some((line, String::new()));
5152        }
5153        syn::visit::visit_item_enum(self, node);
5154    }
5155
5156    fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
5157        if self.target_type == "function" && node.sig.ident.to_string() == self.target_name {
5158            // Use fn_token span to get the line where "fn" keyword appears
5159            let line = node.sig.fn_token.span.start().line;
5160            self.found_position = Some((line, String::new()));
5161        }
5162        syn::visit::visit_item_fn(self, node);
5163    }
5164}
5165
5166impl RustEditor {
5167    /// Add a documentation comment to a target item using surgical editing
5168    pub fn add_doc_comment_surgical(
5169        &mut self,
5170        target_type: &str,
5171        target_name: &str,
5172        doc_text: &str,
5173        style: &DocCommentStyle,
5174    ) -> Result<ModificationResult> {
5175        use syn::visit::Visit;
5176
5177        // Find the target item
5178        let mut finder = TargetFinder::new(
5179            target_type.to_string(),
5180            target_name.to_string(),
5181        );
5182        finder.visit_file(&self.syntax_tree);
5183
5184        if let Some((line_num, _indent)) = finder.found_position {
5185            // Lines are 1-indexed from syn, convert to 0-indexed
5186            let line_idx = line_num.saturating_sub(1);
5187
5188            // Generate the doc comment
5189            let comment = generate_doc_comment(doc_text, style);
5190
5191            // Find the actual line in the source
5192            let lines: Vec<&str> = self.content.lines().collect();
5193            if line_idx >= lines.len() {
5194                anyhow::bail!("Target not found at line {}", line_num);
5195            }
5196
5197            let target_line = lines[line_idx].to_string(); // Clone to avoid borrow issues
5198            let target_line_len = target_line.len();
5199
5200            // Detect indentation from the target line
5201            let indent = target_line
5202                .chars()
5203                .take_while(|c| c.is_whitespace())
5204                .collect::<String>();
5205
5206            // Build the new content with comment inserted
5207            let mut new_lines: Vec<String> = lines.iter().map(|s| s.to_string()).collect();
5208
5209            // Insert comment lines before the target
5210            let comment_lines: Vec<String> = comment
5211                .lines()
5212                .map(|line| format!("{}{}", indent, line))
5213                .collect();
5214
5215            // Insert in reverse order to maintain indices
5216            for (i, comment_line) in comment_lines.iter().rev().enumerate() {
5217                new_lines.insert(line_idx, comment_line.clone());
5218            }
5219
5220            // Update content
5221            self.content = new_lines.join("\n");
5222
5223            // Re-parse to update syntax tree
5224            self.syntax_tree = syn::parse_str(&self.content)
5225                .context("Failed to re-parse after adding comment")?;
5226
5227            Ok(ModificationResult {
5228                changed: true,
5229                modified_nodes: vec![BackupNode {
5230                    node_type: target_type.to_string(),
5231                    identifier: target_name.to_string(),
5232                    original_content: target_line,
5233                    location: NodeLocation {
5234                        line: line_num,
5235                        column: 1,
5236                        end_line: line_num,
5237                        end_column: target_line_len,
5238                    },
5239                }],
5240                unmatched_qualified_paths: None,
5241            })
5242        } else {
5243            anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5244        }
5245    }
5246
5247    /// Update an existing documentation comment on a target item using surgical editing
5248    pub fn update_doc_comment_surgical(
5249        &mut self,
5250        target_type: &str,
5251        target_name: &str,
5252        doc_text: &str,
5253        style: &DocCommentStyle,
5254    ) -> Result<ModificationResult> {
5255        use syn::visit::Visit;
5256
5257        // Find the target item
5258        let mut finder = TargetFinder::new(
5259            target_type.to_string(),
5260            target_name.to_string(),
5261        );
5262        finder.visit_file(&self.syntax_tree);
5263
5264        if let Some((line_num, _indent)) = finder.found_position {
5265            // Lines are 1-indexed from syn, convert to 0-indexed
5266            let line_idx = line_num.saturating_sub(1);
5267
5268            // Find the actual line in the source
5269            let lines: Vec<&str> = self.content.lines().collect();
5270            if line_idx >= lines.len() {
5271                anyhow::bail!("Target not found at line {}", line_num);
5272            }
5273
5274            let target_line = lines[line_idx].to_string(); // Clone to avoid borrow issues
5275            let target_line_len = target_line.len();
5276
5277            // Detect indentation from the target line
5278            let indent = target_line
5279                .chars()
5280                .take_while(|c| c.is_whitespace())
5281                .collect::<String>();
5282
5283            // Scan backwards from target to find existing doc comment lines
5284            let mut doc_comment_start = line_idx;
5285            while doc_comment_start > 0 {
5286                let prev_line = lines[doc_comment_start - 1].trim();
5287                if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
5288                   prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
5289                   (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
5290                   prev_line == "*/" {
5291                    doc_comment_start -= 1;
5292                } else {
5293                    break;
5294                }
5295            }
5296
5297            // Build new content with old comments removed and new ones inserted
5298            let mut new_lines: Vec<String> = Vec::new();
5299
5300            // Add lines before the doc comments
5301            for i in 0..doc_comment_start {
5302                new_lines.push(lines[i].to_string());
5303            }
5304
5305            // Generate and add new doc comment
5306            let comment = generate_doc_comment(doc_text, style);
5307            let comment_lines: Vec<String> = comment
5308                .lines()
5309                .map(|line| format!("{}{}", indent, line))
5310                .collect();
5311
5312            for comment_line in comment_lines {
5313                new_lines.push(comment_line);
5314            }
5315
5316            // Add remaining lines (from target onwards)
5317            for i in line_idx..lines.len() {
5318                new_lines.push(lines[i].to_string());
5319            }
5320
5321            // Update content
5322            self.content = new_lines.join("\n");
5323
5324            // Re-parse to update syntax tree
5325            self.syntax_tree = syn::parse_str(&self.content)
5326                .context("Failed to re-parse after updating comment")?;
5327
5328            Ok(ModificationResult {
5329                changed: true,
5330                modified_nodes: vec![BackupNode {
5331                    node_type: target_type.to_string(),
5332                    identifier: target_name.to_string(),
5333                    original_content: target_line,
5334                    location: NodeLocation {
5335                        line: line_num,
5336                        column: 1,
5337                        end_line: line_num,
5338                        end_column: target_line_len,
5339                    },
5340                }],
5341                unmatched_qualified_paths: None,
5342            })
5343        } else {
5344            anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5345        }
5346    }
5347
5348    /// Remove a documentation comment from a target item using surgical editing
5349    pub fn remove_doc_comment_surgical(
5350        &mut self,
5351        target_type: &str,
5352        target_name: &str,
5353    ) -> Result<ModificationResult> {
5354        use syn::visit::Visit;
5355
5356        // Find the target item
5357        let mut finder = TargetFinder::new(
5358            target_type.to_string(),
5359            target_name.to_string(),
5360        );
5361        finder.visit_file(&self.syntax_tree);
5362
5363        if let Some((line_num, _indent)) = finder.found_position {
5364            // Lines are 1-indexed from syn, convert to 0-indexed
5365            let line_idx = line_num.saturating_sub(1);
5366
5367            // Find the actual line in the source
5368            let lines: Vec<&str> = self.content.lines().collect();
5369            if line_idx >= lines.len() {
5370                anyhow::bail!("Target not found at line {}", line_num);
5371            }
5372
5373            let target_line = lines[line_idx].to_string(); // Clone to avoid borrow issues
5374            let target_line_len = target_line.len();
5375
5376            // Scan backwards from target to find existing doc comment lines
5377            let mut doc_comment_start = line_idx;
5378            while doc_comment_start > 0 {
5379                let prev_line = lines[doc_comment_start - 1].trim();
5380                if prev_line.starts_with("///") || prev_line.starts_with("//!") ||
5381                   prev_line.starts_with("/**") || prev_line.starts_with("/*!") ||
5382                   (prev_line.starts_with("*") && !prev_line.starts_with("*/")) ||
5383                   prev_line == "*/" {
5384                    doc_comment_start -= 1;
5385                } else {
5386                    break;
5387                }
5388            }
5389
5390            // Build new content with doc comments removed
5391            let mut new_lines: Vec<String> = Vec::new();
5392
5393            // Add lines before the doc comments
5394            for i in 0..doc_comment_start {
5395                new_lines.push(lines[i].to_string());
5396            }
5397
5398            // Skip the doc comment lines (from doc_comment_start to line_idx)
5399
5400            // Add remaining lines (from target onwards)
5401            for i in line_idx..lines.len() {
5402                new_lines.push(lines[i].to_string());
5403            }
5404
5405            // Update content
5406            self.content = new_lines.join("\n");
5407
5408            // Re-parse to update syntax tree
5409            self.syntax_tree = syn::parse_str(&self.content)
5410                .context("Failed to re-parse after removing comment")?;
5411
5412            Ok(ModificationResult {
5413                changed: true,
5414                modified_nodes: vec![BackupNode {
5415                    node_type: target_type.to_string(),
5416                    identifier: target_name.to_string(),
5417                    original_content: target_line,
5418                    location: NodeLocation {
5419                        line: line_num,
5420                        column: 1,
5421                        end_line: line_num,
5422                        end_column: target_line_len,
5423                    },
5424                }],
5425                unmatched_qualified_paths: None,
5426            })
5427        } else {
5428            anyhow::bail!("Target {} '{}' not found", target_type, target_name)
5429        }
5430    }
5431}