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