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