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 prettyplease;
12
13pub struct RustEditor {
14    content: String,
15    syntax_tree: File,
16    line_offsets: Vec<usize>, // Byte offset for each line start
17}
18
19impl RustEditor {
20    pub fn new(content: &str) -> Result<Self> {
21        let syntax_tree: File = syn::parse_str(content)
22            .context("Failed to parse Rust code")?;
23
24        let line_offsets = Self::compute_line_offsets(content);
25
26        Ok(Self {
27            content: content.to_string(),
28            syntax_tree,
29            line_offsets,
30        })
31    }
32
33    /// Format a field without extra spaces (e.g., "pub name: String" not "pub name : String")
34    fn format_field(field: &Field) -> String {
35        let mut result = String::new();
36
37        // Add visibility
38        if let syn::Visibility::Public(_) = field.vis {
39            result.push_str("pub ");
40        }
41
42        // Add field name
43        if let Some(ident) = &field.ident {
44            result.push_str(&ident.to_string());
45        }
46
47        // Add colon and type (no space before colon)
48        result.push_str(": ");
49
50        // Format type without extra spaces
51        let type_str = field.ty.to_token_stream().to_string();
52        let type_str = type_str.replace(" < ", "<").replace(" >", ">");
53        result.push_str(&type_str);
54
55        result
56    }
57    
58    fn compute_line_offsets(content: &str) -> Vec<usize> {
59        let mut offsets = vec![0];
60        for (i, ch) in content.char_indices() {
61            if ch == '\n' {
62                offsets.push(i + 1);
63            }
64        }
65        offsets
66    }
67    
68    pub fn apply_operation(&mut self, op: &Operation) -> Result<ModificationResult> {
69        match op {
70            Operation::AddStructField(op) => self.add_struct_field(op),
71            Operation::UpdateStructField(op) => self.update_struct_field(op),
72            Operation::RemoveStructField(op) => self.remove_struct_field(op),
73            Operation::AddStructLiteralField(op) => self.add_struct_literal_field(op),
74            Operation::AddEnumVariant(op) => self.add_enum_variant(op),
75            Operation::UpdateEnumVariant(op) => self.update_enum_variant(op),
76            Operation::RemoveEnumVariant(op) => self.remove_enum_variant(op),
77            Operation::AddMatchArm(op) => self.add_match_arm(op),
78            Operation::UpdateMatchArm(op) => self.update_match_arm(op),
79            Operation::RemoveMatchArm(op) => self.remove_match_arm(op),
80            Operation::AddImplMethod(op) => self.add_impl_method(op),
81            Operation::AddUseStatement(op) => self.add_use_statement(op),
82            Operation::AddDerive(op) => self.add_derive(op),
83            Operation::Transform(op) => self.transform(op),
84        }
85    }
86    
87    pub(crate) fn add_struct_field(&mut self, op: &AddStructFieldOp) -> Result<ModificationResult> {
88        let mut modified_nodes = Vec::new();
89
90        // Find the struct and clone it to avoid borrowing issues
91        let item_struct = self.syntax_tree.items.iter()
92            .find_map(|item| {
93                if let Item::Struct(s) = item {
94                    if s.ident == op.struct_name {
95                        return Some(s.clone());
96                    }
97                }
98                None
99            })
100            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
101
102        // Check if the struct matches the where filter (if specified)
103        if let Some(ref where_filter) = op.where_filter {
104            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
105                // Struct doesn't match filter - skip without error
106                return Ok(ModificationResult {
107                    changed: false,
108                    modified_nodes: vec![],
109                });
110            }
111        }
112
113        // If literal_default is NOT provided, only modify the definition
114        if op.literal_default.is_none() {
115            // Create backup of original struct before modification
116            let backup_node = BackupNode {
117                node_type: "ItemStruct".to_string(),
118                identifier: op.struct_name.clone(),
119                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
120                location: self.span_to_location(item_struct.span()),
121            };
122
123            // Insert the field into the struct definition
124            let modified = self.insert_struct_field(&item_struct, op)
125                .context("Failed to add field to struct definition")?;
126
127            if !modified {
128                return Ok(ModificationResult {
129                    changed: false,
130                    modified_nodes: vec![],
131                });
132            }
133
134            return Ok(ModificationResult {
135                changed: true,
136                modified_nodes: vec![backup_node],
137            });
138        }
139
140        // If literal_default IS provided:
141        // 1. Try to add to definition (idempotent - silently skips if field exists OR if field_def is incomplete)
142        // 2. Always update literals
143        let literal_default = op.literal_default.as_ref().unwrap();
144
145        // Check if field_def contains a type (has ':')
146        // If it doesn't, skip definition modification (literals-only mode)
147        let has_type = op.field_def.contains(':');
148
149        let mut def_modified = false;
150        if has_type {
151            // Create backup before any modifications
152            let backup_node = BackupNode {
153                node_type: "ItemStruct".to_string(),
154                identifier: op.struct_name.clone(),
155                original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
156                location: self.span_to_location(item_struct.span()),
157            };
158
159            // Try to insert field into definition (idempotent - returns false if already exists)
160            def_modified = self.insert_struct_field(&item_struct, op)
161                .context("Failed to add field to struct definition")?;
162
163            if def_modified {
164                modified_nodes.push(backup_node);
165                // Re-parse the content to update syntax_tree with the struct field changes
166                self.syntax_tree = syn::parse_str(&self.content)
167                    .context("Failed to re-parse content after adding struct field")?;
168                self.line_offsets = Self::compute_line_offsets(&self.content);
169            }
170        }
171
172        // Always update literals when literal_default is provided
173        // Extract field name from field_def (e.g., "return_type: Option<Type>" -> "return_type" or just "return_type")
174        let field_name = op.field_def.split(':')
175            .next()
176            .map(|s| s.trim().to_string())
177            .context("Failed to extract field name from field definition")?;
178
179        // Create the AddStructLiteralFieldOp
180        let literal_op = AddStructLiteralFieldOp {
181            struct_name: op.struct_name.clone(),
182            field_def: format!("{}: {}", field_name, literal_default),
183            position: op.position.clone(),
184        };
185
186        // Update all struct literals
187        let literal_result = self.add_struct_literal_field(&literal_op)
188            .context("Failed to update struct literals")?;
189        modified_nodes.extend(literal_result.modified_nodes);
190
191        Ok(ModificationResult {
192            changed: true,
193            modified_nodes,
194        })
195    }
196    
197    fn insert_struct_field(&mut self, item_struct: &ItemStruct, op: &AddStructFieldOp) -> Result<bool> {
198        if let Fields::Named(ref fields) = item_struct.fields {
199            // Parse the new field
200            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
201            let dummy: ItemStruct = parse_str(&field_code)
202                .context("Failed to parse field definition")?;
203
204            let new_field = if let Fields::Named(ref nf) = dummy.fields {
205                nf.named.first()
206                    .context("No field found in definition")?
207                    .clone()
208            } else {
209                anyhow::bail!("Expected named field");
210            };
211
212            // Check if field already exists
213            let new_field_name = new_field.ident.as_ref()
214                .map(|i| i.to_string())
215                .context("Field must have a name")?;
216
217            if fields.named.iter().any(|f| {
218                f.ident.as_ref().map(|i| i.to_string()) == Some(new_field_name.clone())
219            }) {
220                // Field already exists, skip adding
221                return Ok(false);
222            }
223            
224            // Determine insertion point
225            let insert_pos = match &op.position {
226                InsertPosition::First => {
227                    if let Some(first_field) = fields.named.first() {
228                        self.span_to_byte_offset(first_field.span().start())
229                    } else {
230                        // Empty struct, insert after the opening brace
231                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
232                        brace_pos + 1
233                    }
234                }
235                InsertPosition::Last => {
236                    if let Some(last_field) = fields.named.last() {
237                        let end = self.span_to_byte_offset(last_field.span().end());
238                        // Find the comma or end
239                        self.find_after_field_end(end)
240                    } else {
241                        // Empty struct
242                        let brace_pos = self.span_to_byte_offset(fields.brace_token.span.join().start());
243                        brace_pos + 1
244                    }
245                }
246                InsertPosition::After(name) => {
247                    let field = fields.named.iter()
248                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
249                        .with_context(|| format!("Field '{}' not found", name))?;
250                    let end = self.span_to_byte_offset(field.span().end());
251                    self.find_after_field_end(end)
252                }
253                InsertPosition::Before(name) => {
254                    let field = fields.named.iter()
255                        .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(name.clone()))
256                        .with_context(|| format!("Field '{}' not found", name))?;
257                    self.span_to_byte_offset(field.span().start())
258                }
259            };
260            
261            // Format the new field
262            let indent = self.get_indentation(insert_pos);
263            let field_str = Self::format_field(&new_field);
264            let insert_text = if matches!(op.position, InsertPosition::First) {
265                format!("\n{}{},", indent, field_str)
266            } else {
267                format!("\n{}{},", indent, field_str)
268            };
269
270            self.content.insert_str(insert_pos, &insert_text);
271            return Ok(true);
272        }
273        
274        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
275    }
276
277    pub(crate) fn update_struct_field(&mut self, op: &UpdateStructFieldOp) -> Result<ModificationResult> {
278        // Find the struct and clone it to avoid borrowing issues
279        let item_struct = self.syntax_tree.items.iter()
280            .find_map(|item| {
281                if let Item::Struct(s) = item {
282                    if s.ident == op.struct_name {
283                        return Some(s.clone());
284                    }
285                }
286                None
287            })
288            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
289
290        // Check if the struct matches the where filter (if specified)
291        if let Some(ref where_filter) = op.where_filter {
292            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
293                // Struct doesn't match filter - skip without error
294                return Ok(ModificationResult {
295                    changed: false,
296                    modified_nodes: vec![],
297                });
298            }
299        }
300
301        // Create backup of original struct before modification
302        let backup_node = BackupNode {
303            node_type: "ItemStruct".to_string(),
304            identifier: op.struct_name.clone(),
305            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
306            location: self.span_to_location(item_struct.span()),
307        };
308
309        let modified = self.replace_struct_field(&item_struct, op)?;
310
311        Ok(ModificationResult {
312            changed: modified,
313            modified_nodes: if modified { vec![backup_node] } else { vec![] },
314        })
315    }
316
317    fn replace_struct_field(&mut self, item_struct: &ItemStruct, op: &UpdateStructFieldOp) -> Result<bool> {
318        if let Fields::Named(ref fields) = item_struct.fields {
319            // Parse the new field definition to get the field name
320            let field_code = format!("struct Dummy {{ {} }}", op.field_def);
321            let dummy: ItemStruct = parse_str(&field_code)
322                .context("Failed to parse field definition")?;
323
324            let new_field = if let Fields::Named(ref nf) = dummy.fields {
325                nf.named.first()
326                    .context("No field found in definition")?
327                    .clone()
328            } else {
329                anyhow::bail!("Expected named field");
330            };
331
332            // Extract the field name from the parsed field
333            let field_name = new_field.ident.as_ref()
334                .map(|i| i.to_string())
335                .context("Field must have a name")?;
336
337            // Find the existing field
338            let existing_field = fields.named.iter()
339                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(field_name.clone()))
340                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", field_name, op.struct_name))?;
341
342            // Get the span of the existing field
343            let start = self.span_to_byte_offset(existing_field.span().start());
344            let end = self.span_to_byte_offset(existing_field.span().end());
345
346            // Format and replace the field
347            let new_field_str = Self::format_field(&new_field);
348
349            // Remove the old field and insert the new one
350            self.content.replace_range(start..end, &new_field_str);
351
352            return Ok(true);
353        }
354
355        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
356    }
357
358    pub(crate) fn remove_struct_field(&mut self, op: &RemoveStructFieldOp) -> Result<ModificationResult> {
359        // Find the struct and clone it to avoid borrowing issues
360        let item_struct = self.syntax_tree.items.iter()
361            .find_map(|item| {
362                if let Item::Struct(s) = item {
363                    if s.ident == op.struct_name {
364                        return Some(s.clone());
365                    }
366                }
367                None
368            })
369            .ok_or_else(|| anyhow::anyhow!("Struct '{}' not found", op.struct_name))?;
370
371        // Check if the struct matches the where filter (if specified)
372        if let Some(ref where_filter) = op.where_filter {
373            if !self.matches_where_filter(&item_struct.attrs, where_filter)? {
374                // Struct doesn't match filter - skip without error
375                return Ok(ModificationResult {
376                    changed: false,
377                    modified_nodes: vec![],
378                });
379            }
380        }
381
382        // Create backup of original struct before modification
383        let backup_node = BackupNode {
384            node_type: "ItemStruct".to_string(),
385            identifier: op.struct_name.clone(),
386            original_content: self.unparse_item(&Item::Struct(item_struct.clone())),
387            location: self.span_to_location(item_struct.span()),
388        };
389
390        if let Fields::Named(ref fields) = item_struct.fields {
391            // Find the field to remove
392            let field_to_remove = fields.named.iter()
393                .find(|f| f.ident.as_ref().map(|i| i.to_string()) == Some(op.field_name.clone()))
394                .ok_or_else(|| anyhow::anyhow!("Field '{}' not found in struct '{}'", op.field_name, op.struct_name))?;
395
396            // Get the span including the comma
397            let start = self.span_to_byte_offset(field_to_remove.span().start());
398            let mut end = self.span_to_byte_offset(field_to_remove.span().end());
399
400            // Find and include the comma and any trailing whitespace/newline
401            while end < self.content.len() {
402                match self.content.as_bytes()[end] as char {
403                    ',' => {
404                        end += 1;
405                        // Also consume the newline after the comma if present
406                        if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
407                            end += 1;
408                        }
409                        break;
410                    }
411                    ' ' | '\t' => end += 1,
412                    '\n' => {
413                        end += 1;
414                        break;
415                    }
416                    _ => break,
417                }
418            }
419
420            // Also need to remove leading whitespace/indentation on the same line
421            let mut line_start = start;
422            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
423                line_start -= 1;
424            }
425
426            // Check if there's only whitespace between line_start and start
427            let before_field = &self.content[line_start..start];
428            if before_field.trim().is_empty() {
429                // Remove the whole line
430                self.content.replace_range(line_start..end, "");
431            } else {
432                // Just remove the field and comma
433                self.content.replace_range(start..end, "");
434            }
435
436            return Ok(ModificationResult {
437                changed: true,
438                modified_nodes: vec![backup_node],
439            });
440        }
441
442        anyhow::bail!("Struct '{}' does not have named fields", op.struct_name)
443    }
444
445    pub(crate) fn add_struct_literal_field(&mut self, op: &AddStructLiteralFieldOp) -> Result<ModificationResult> {
446        // Parse the field name from field_def (e.g., "return_type: None" -> "return_type")
447        let field_name = op.field_def.split(':')
448            .next()
449            .map(|s| s.trim().to_string())
450            .context("Field definition must contain ':'")?;
451
452        // Collect backups of all struct literal expressions that will be modified
453        let backup_nodes = self.collect_struct_literal_backups(&op.struct_name);
454
455        // Use a visitor to find and modify all struct literals
456        let mut visitor = StructLiteralFieldAdder {
457            struct_name: op.struct_name.clone(),
458            field_def: op.field_def.clone(),
459            field_name,
460            position: op.position.clone(),
461            modified: false,
462        };
463
464        visitor.visit_file_mut(&mut self.syntax_tree);
465
466        if visitor.modified {
467            // Reformat the entire file for struct literals
468            self.content = prettyplease::unparse(&self.syntax_tree);
469            Ok(ModificationResult {
470                changed: true,
471                modified_nodes: backup_nodes,
472            })
473        } else {
474            Ok(ModificationResult {
475                changed: false,
476                modified_nodes: vec![],
477            })
478        }
479    }
480
481    /// Collect backups of all struct literal expressions for a given struct name
482    fn collect_struct_literal_backups(&self, struct_name: &str) -> Vec<BackupNode> {
483        use syn::visit::Visit;
484
485        struct LiteralCollector {
486            struct_name: String,
487            backups: Vec<BackupNode>,
488            counter: usize,
489        }
490
491        impl<'ast> Visit<'ast> for LiteralCollector {
492            fn visit_expr(&mut self, node: &'ast Expr) {
493                if let Expr::Struct(expr_struct) = node {
494                    // Match based on pattern:
495                    // - "Rectangle" → only Rectangle { ... } (no :: prefix)
496                    // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
497                    // - "View::Rectangle" → exact match only View::Rectangle
498
499                    let matches = if self.struct_name.contains("::") {
500                        // Pattern contains :: - check for exact or wildcard match
501                        if self.struct_name.starts_with("*::") {
502                            // Wildcard: *::Rectangle matches any path ending with Rectangle
503                            let target_name = &self.struct_name[3..]; // Skip "*::"
504                            expr_struct.path.segments.last()
505                                .map(|seg| seg.ident.to_string() == target_name)
506                                .unwrap_or(false)
507                        } else {
508                            // Exact path match: View::Rectangle
509                            let path_str = expr_struct.path.segments.iter()
510                                .map(|seg| seg.ident.to_string())
511                                .collect::<Vec<_>>()
512                                .join("::");
513                            path_str == self.struct_name
514                        }
515                    } else {
516                        // No :: in pattern - only match pure struct literals (no path qualifier)
517                        expr_struct.path.segments.len() == 1
518                            && expr_struct.path.segments.last()
519                                .map(|seg| seg.ident.to_string() == self.struct_name)
520                                .unwrap_or(false)
521                    };
522
523                    if matches {
524                        self.backups.push(BackupNode {
525                            node_type: "ExprStruct".to_string(),
526                            identifier: format!("{}#{}", self.struct_name, self.counter),
527                            original_content: expr_struct.to_token_stream().to_string(),
528                            location: NodeLocation {
529                                line: 0, // We don't have precise location info in visitor
530                                column: 0,
531                                end_line: 0,
532                                end_column: 0,
533                            },
534                        });
535                        self.counter += 1;
536                    }
537                }
538                syn::visit::visit_expr(self, node);
539            }
540        }
541
542        let mut collector = LiteralCollector {
543            struct_name: struct_name.to_string(),
544            backups: Vec::new(),
545            counter: 0,
546        };
547
548        collector.visit_file(&self.syntax_tree);
549        collector.backups
550    }
551
552    pub(crate) fn add_enum_variant(&mut self, op: &AddEnumVariantOp) -> Result<ModificationResult> {
553        // Find the enum and clone it to avoid borrowing issues
554        let item_enum = self.syntax_tree.items.iter()
555            .find_map(|item| {
556                if let Item::Enum(e) = item {
557                    if e.ident == op.enum_name {
558                        return Some(e.clone());
559                    }
560                }
561                None
562            })
563            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
564
565        // Check if the enum matches the where filter (if specified)
566        if let Some(ref where_filter) = op.where_filter {
567            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
568                // Enum doesn't match filter - skip without error
569                return Ok(ModificationResult {
570                    changed: false,
571                    modified_nodes: vec![],
572                });
573            }
574        }
575
576        // Create backup of original enum before modification
577        let backup_node = BackupNode {
578            node_type: "ItemEnum".to_string(),
579            identifier: op.enum_name.clone(),
580            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
581            location: self.span_to_location(item_enum.span()),
582        };
583
584        let modified = self.insert_enum_variant(&item_enum, op)?;
585
586        Ok(ModificationResult {
587            changed: modified,
588            modified_nodes: if modified { vec![backup_node] } else { vec![] },
589        })
590    }
591    
592    fn insert_enum_variant(&mut self, item_enum: &ItemEnum, op: &AddEnumVariantOp) -> Result<bool> {
593        // Parse the new variant
594        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
595        let dummy: ItemEnum = parse_str(&variant_code)
596            .context("Failed to parse variant definition")?;
597
598        let new_variant = dummy.variants.first()
599            .context("No variant found in definition")?
600            .clone();
601
602        // Check if variant already exists
603        let variant_name = new_variant.ident.to_string();
604        if item_enum.variants.iter().any(|v| v.ident.to_string() == variant_name) {
605            // Variant already exists, skip adding
606            return Ok(false);
607        }
608
609        // Determine insertion point
610        let insert_pos = match &op.position {
611            InsertPosition::First => {
612                if let Some(first_var) = item_enum.variants.first() {
613                    self.span_to_byte_offset(first_var.span().start())
614                } else {
615                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
616                    brace_pos + 1
617                }
618            }
619            InsertPosition::Last => {
620                if let Some(last_var) = item_enum.variants.last() {
621                    let end = self.span_to_byte_offset(last_var.span().end());
622                    self.find_after_field_end(end)
623                } else {
624                    let brace_pos = self.span_to_byte_offset(item_enum.brace_token.span.join().start());
625                    brace_pos + 1
626                }
627            }
628            InsertPosition::After(name) => {
629                let variant = item_enum.variants.iter()
630                    .find(|v| v.ident.to_string() == *name)
631                    .with_context(|| format!("Variant '{}' not found", name))?;
632                let end = self.span_to_byte_offset(variant.span().end());
633                self.find_after_field_end(end)
634            }
635            InsertPosition::Before(name) => {
636                let variant = item_enum.variants.iter()
637                    .find(|v| v.ident.to_string() == *name)
638                    .with_context(|| format!("Variant '{}' not found", name))?;
639                self.span_to_byte_offset(variant.span().start())
640            }
641        };
642        
643        let indent = self.get_indentation(insert_pos);
644        let variant_str = new_variant.to_token_stream().to_string();
645        let insert_text = format!("\n{}{},", indent, variant_str);
646        
647        self.content.insert_str(insert_pos, &insert_text);
648        Ok(true)
649    }
650
651    fn update_enum_variant(&mut self, op: &UpdateEnumVariantOp) -> Result<ModificationResult> {
652        // Find the enum and clone it
653        let item_enum = self.syntax_tree.items.iter()
654            .find_map(|item| {
655                if let Item::Enum(e) = item {
656                    if e.ident == op.enum_name {
657                        return Some(e.clone());
658                    }
659                }
660                None
661            })
662            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
663
664        // Check if the enum matches the where filter (if specified)
665        if let Some(ref where_filter) = op.where_filter {
666            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
667                // Enum doesn't match filter - skip without error
668                return Ok(ModificationResult {
669                    changed: false,
670                    modified_nodes: vec![],
671                });
672            }
673        }
674
675        // Create backup of original enum before modification
676        let backup_node = BackupNode {
677            node_type: "ItemEnum".to_string(),
678            identifier: op.enum_name.clone(),
679            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
680            location: self.span_to_location(item_enum.span()),
681        };
682
683        // Parse the new variant to get its name
684        let variant_code = format!("enum Dummy {{ {} }}", op.variant_def);
685        let dummy: ItemEnum = parse_str(&variant_code)
686            .context("Failed to parse variant definition")?;
687
688        let new_variant = dummy.variants.first()
689            .context("No variant found in definition")?
690            .clone();
691
692        let variant_name = new_variant.ident.to_string();
693
694        // Find the existing variant
695        let existing_variant = item_enum.variants.iter()
696            .find(|v| v.ident.to_string() == variant_name)
697            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", variant_name, op.enum_name))?;
698
699        // Get the span
700        let start = self.span_to_byte_offset(existing_variant.span().start());
701        let end = self.span_to_byte_offset(existing_variant.span().end());
702
703        // Format and replace
704        let variant_str = new_variant.to_token_stream().to_string();
705        self.content.replace_range(start..end, &variant_str);
706
707        Ok(ModificationResult {
708            changed: true,
709            modified_nodes: vec![backup_node],
710        })
711    }
712
713    pub(crate) fn remove_enum_variant(&mut self, op: &RemoveEnumVariantOp) -> Result<ModificationResult> {
714        // Find the enum
715        let item_enum = self.syntax_tree.items.iter()
716            .find_map(|item| {
717                if let Item::Enum(e) = item {
718                    if e.ident == op.enum_name {
719                        return Some(e.clone());
720                    }
721                }
722                None
723            })
724            .ok_or_else(|| anyhow::anyhow!("Enum '{}' not found", op.enum_name))?;
725
726        // Check if the enum matches the where filter (if specified)
727        if let Some(ref where_filter) = op.where_filter {
728            if !self.matches_where_filter(&item_enum.attrs, where_filter)? {
729                // Enum doesn't match filter - skip without error
730                return Ok(ModificationResult {
731                    changed: false,
732                    modified_nodes: vec![],
733                });
734            }
735        }
736
737        // Create backup of original enum before modification
738        let backup_node = BackupNode {
739            node_type: "ItemEnum".to_string(),
740            identifier: op.enum_name.clone(),
741            original_content: self.unparse_item(&Item::Enum(item_enum.clone())),
742            location: self.span_to_location(item_enum.span()),
743        };
744
745        // Find the variant to remove
746        let variant_to_remove = item_enum.variants.iter()
747            .find(|v| v.ident.to_string() == op.variant_name)
748            .ok_or_else(|| anyhow::anyhow!("Variant '{}' not found in enum '{}'", op.variant_name, op.enum_name))?;
749
750        // Get the span including comma
751        let start = self.span_to_byte_offset(variant_to_remove.span().start());
752        let mut end = self.span_to_byte_offset(variant_to_remove.span().end());
753
754        // Find and include the comma and trailing whitespace
755        while end < self.content.len() {
756            match self.content.as_bytes()[end] as char {
757                ',' => {
758                    end += 1;
759                    if end < self.content.len() && self.content.as_bytes()[end] == b'\n' {
760                        end += 1;
761                    }
762                    break;
763                }
764                ' ' | '\t' => end += 1,
765                '\n' => {
766                    end += 1;
767                    break;
768                }
769                _ => break,
770            }
771        }
772
773        // Remove leading whitespace on the line
774        let mut line_start = start;
775        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
776            line_start -= 1;
777        }
778
779        let before_variant = &self.content[line_start..start];
780        if before_variant.trim().is_empty() {
781            self.content.replace_range(line_start..end, "");
782        } else {
783            self.content.replace_range(start..end, "");
784        }
785
786        Ok(ModificationResult {
787            changed: true,
788            modified_nodes: vec![backup_node],
789        })
790    }
791
792    pub(crate) fn add_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
793        if op.auto_detect {
794            // Auto-detect mode: find all missing enum variants
795            self.add_missing_match_arms(op)
796        } else {
797            // Normal mode: add a single match arm
798            self.add_single_match_arm(op)
799        }
800    }
801
802    fn add_single_match_arm(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
803        // Parse the pattern and body by creating a dummy match expression
804        let dummy_match = format!("match () {{ {} => {}, }}", op.pattern, op.body);
805        let expr: syn::Expr = parse_str(&dummy_match)
806            .with_context(|| format!("Failed to parse pattern/body: {} => {}", op.pattern, op.body))?;
807
808        // Extract the arm from the dummy match
809        let arm = if let syn::Expr::Match(match_expr) = expr {
810            match_expr.arms.into_iter().next()
811                .context("Failed to extract arm from dummy match")?
812        } else {
813            anyhow::bail!("Expected match expression");
814        };
815
816        // Collect backup of function before modification
817        let backup_node = if let Some(ref fn_name) = op.function_name {
818            self.get_function_backup(fn_name)?
819        } else {
820            // If no function specified, we'll backup all modified functions later
821            // For now, create a generic backup
822            BackupNode {
823                node_type: "Unknown".to_string(),
824                identifier: "match_expression".to_string(),
825                original_content: String::new(),
826                location: NodeLocation {
827                    line: 0,
828                    column: 0,
829                    end_line: 0,
830                    end_column: 0,
831                },
832            }
833        };
834
835        // Find and modify match expressions
836        let mut visitor = MatchArmAdder {
837            target_function: op.function_name.clone(),
838            arm_to_add: arm,
839            modified: false,
840            current_function: None,
841            modified_function: None,
842        };
843
844        visitor.visit_file_mut(&mut self.syntax_tree);
845
846        if visitor.modified {
847            // Replace just the modified function
848            self.replace_modified_functions(&visitor.modified_function)?;
849            Ok(ModificationResult {
850                changed: true,
851                modified_nodes: vec![backup_node],
852            })
853        } else {
854            Ok(ModificationResult {
855                changed: false,
856                modified_nodes: vec![],
857            })
858        }
859    }
860
861    /// Format a single item to string using prettyplease
862    fn unparse_item(&self, item: &Item) -> String {
863        let temp_file = syn::File {
864            shebang: None,
865            attrs: Vec::new(),
866            items: vec![item.clone()],
867        };
868        prettyplease::unparse(&temp_file).trim().to_string()
869    }
870
871    /// Get backup of a function before modification
872    fn get_function_backup(&self, fn_name: &str) -> Result<BackupNode> {
873        for item in &self.syntax_tree.items {
874            if let Item::Fn(f) = item {
875                if f.sig.ident == fn_name {
876                    return Ok(BackupNode {
877                        node_type: "ItemFn".to_string(),
878                        identifier: fn_name.to_string(),
879                        original_content: self.unparse_item(&Item::Fn(f.clone())),
880                        location: self.span_to_location(f.span()),
881                    });
882                }
883            }
884        }
885        anyhow::bail!("Function '{}' not found", fn_name)
886    }
887
888    fn add_missing_match_arms(&mut self, op: &AddMatchArmOp) -> Result<ModificationResult> {
889        // Get the enum name
890        let enum_name = op.enum_name.as_ref()
891            .ok_or_else(|| anyhow::anyhow!("enum_name is required for auto-detect"))?;
892
893        // Find all enum variants
894        let enum_variants = self.find_enum_variants(enum_name)?;
895
896        if enum_variants.is_empty() {
897            anyhow::bail!("Enum '{}' not found or has no variants", enum_name);
898        }
899
900        // Find existing match arms
901        let existing_patterns = self.find_existing_match_patterns(&op.function_name);
902
903        // Determine missing variants
904        let mut missing_variants = Vec::new();
905        for variant in &enum_variants {
906            let pattern = format!("{}::{}", enum_name, variant);
907            let pattern_normalized = pattern.replace(" ", "");
908
909            let exists = existing_patterns.iter().any(|p| {
910                p.replace(" ", "") == pattern_normalized
911            });
912
913            if !exists {
914                missing_variants.push(variant.clone());
915            }
916        }
917
918        if missing_variants.is_empty() {
919            println!("All enum variants already covered in match expressions");
920            return Ok(ModificationResult {
921                changed: false,
922                modified_nodes: vec![],
923            });
924        }
925
926        // Get backup of function before modification
927        let backup_node = if let Some(ref fn_name) = op.function_name {
928            self.get_function_backup(fn_name)?
929        } else {
930            BackupNode {
931                node_type: "Unknown".to_string(),
932                identifier: "match_expression".to_string(),
933                original_content: String::new(),
934                location: NodeLocation {
935                    line: 0,
936                    column: 0,
937                    end_line: 0,
938                    end_column: 0,
939                },
940            }
941        };
942
943        // Add ALL missing match arms in one pass using a visitor
944        let mut arms_to_add = Vec::new();
945        for variant in &missing_variants {
946            let pattern = format!("{}::{}", enum_name, variant);
947            let dummy_match = format!("match () {{ {} => {}, }}", pattern, op.body);
948            let expr: syn::Expr = parse_str(&dummy_match)
949                .with_context(|| format!("Failed to parse pattern/body: {} => {}", pattern, op.body))?;
950
951            if let syn::Expr::Match(match_expr) = expr {
952                if let Some(arm) = match_expr.arms.into_iter().next() {
953                    arms_to_add.push((pattern.clone(), arm));
954                }
955            }
956        }
957
958        // Find and modify match expressions with all arms at once
959        let mut visitor = MultiMatchArmAdder {
960            target_function: op.function_name.clone(),
961            arms_to_add,
962            modified: false,
963            current_function: None,
964            modified_function: None,
965        };
966
967        visitor.visit_file_mut(&mut self.syntax_tree);
968
969        if visitor.modified {
970            // Print what was added
971            for variant in &missing_variants {
972                println!("Added match arm for: {}::{}", enum_name, variant);
973            }
974
975            // Replace just the modified function
976            self.replace_modified_functions(&visitor.modified_function)?;
977            Ok(ModificationResult {
978                changed: true,
979                modified_nodes: vec![backup_node],
980            })
981        } else {
982            Ok(ModificationResult {
983                changed: false,
984                modified_nodes: vec![],
985            })
986        }
987    }
988
989    fn find_enum_variants(&self, enum_name: &str) -> Result<Vec<String>> {
990        // Find the enum in the syntax tree
991        for item in &self.syntax_tree.items {
992            if let Item::Enum(e) = item {
993                if e.ident == enum_name {
994                    let variants: Vec<String> = e.variants.iter()
995                        .map(|v| v.ident.to_string())
996                        .collect();
997                    return Ok(variants);
998                }
999            }
1000        }
1001
1002        Ok(Vec::new())
1003    }
1004
1005    fn find_existing_match_patterns(&self, function_name: &Option<String>) -> Vec<String> {
1006        use syn::visit::Visit;
1007
1008        struct PatternCollector {
1009            target_function: Option<String>,
1010            current_function: Option<String>,
1011            patterns: Vec<String>,
1012        }
1013
1014        impl<'ast> Visit<'ast> for PatternCollector {
1015            fn visit_item_fn(&mut self, node: &'ast syn::ItemFn) {
1016                let prev_fn = self.current_function.clone();
1017                self.current_function = Some(node.sig.ident.to_string());
1018                syn::visit::visit_item_fn(self, node);
1019                self.current_function = prev_fn;
1020            }
1021
1022            fn visit_expr_match(&mut self, node: &'ast ExprMatch) {
1023                // Check if we're in the right function (if specified)
1024                if let Some(ref target) = self.target_function {
1025                    if self.current_function.as_ref() != Some(target) {
1026                        syn::visit::visit_expr_match(self, node);
1027                        return;
1028                    }
1029                }
1030
1031                // Collect all patterns
1032                for arm in &node.arms {
1033                    self.patterns.push(arm.pat.to_token_stream().to_string());
1034                }
1035
1036                syn::visit::visit_expr_match(self, node);
1037            }
1038        }
1039
1040        let mut collector = PatternCollector {
1041            target_function: function_name.clone(),
1042            current_function: None,
1043            patterns: Vec::new(),
1044        };
1045
1046        collector.visit_file(&self.syntax_tree);
1047        collector.patterns
1048    }
1049
1050    pub(crate) fn update_match_arm(&mut self, op: &UpdateMatchArmOp) -> Result<ModificationResult> {
1051        // Get backup of function before modification
1052        let backup_node = if let Some(ref fn_name) = op.function_name {
1053            self.get_function_backup(fn_name)?
1054        } else {
1055            BackupNode {
1056                node_type: "Unknown".to_string(),
1057                identifier: "match_expression".to_string(),
1058                original_content: String::new(),
1059                location: NodeLocation {
1060                    line: 0,
1061                    column: 0,
1062                    end_line: 0,
1063                    end_column: 0,
1064                },
1065            }
1066        };
1067
1068        // Parse the new body
1069        let new_body: syn::Expr = parse_str(&op.new_body)
1070            .with_context(|| format!("Failed to parse new body: {}", op.new_body))?;
1071
1072        // Find and modify match expressions
1073        let mut visitor = MatchArmUpdater {
1074            target_function: op.function_name.clone(),
1075            pattern_to_match: op.pattern.clone(),
1076            new_body,
1077            modified: false,
1078            current_function: None,
1079            modified_function: None,
1080        };
1081
1082        visitor.visit_file_mut(&mut self.syntax_tree);
1083
1084        if visitor.modified {
1085            // Replace just the modified function
1086            self.replace_modified_functions(&visitor.modified_function)?;
1087            Ok(ModificationResult {
1088                changed: true,
1089                modified_nodes: vec![backup_node],
1090            })
1091        } else {
1092            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1093        }
1094    }
1095
1096    pub(crate) fn remove_match_arm(&mut self, op: &RemoveMatchArmOp) -> Result<ModificationResult> {
1097        // Get backup of function before modification
1098        let backup_node = if let Some(ref fn_name) = op.function_name {
1099            self.get_function_backup(fn_name)?
1100        } else {
1101            BackupNode {
1102                node_type: "Unknown".to_string(),
1103                identifier: "match_expression".to_string(),
1104                original_content: String::new(),
1105                location: NodeLocation {
1106                    line: 0,
1107                    column: 0,
1108                    end_line: 0,
1109                    end_column: 0,
1110                },
1111            }
1112        };
1113
1114        // Find and modify match expressions
1115        let mut visitor = MatchArmRemover {
1116            target_function: op.function_name.clone(),
1117            pattern_to_remove: op.pattern.clone(),
1118            modified: false,
1119            current_function: None,
1120            modified_function: None,
1121        };
1122
1123        visitor.visit_file_mut(&mut self.syntax_tree);
1124
1125        if visitor.modified {
1126            // Replace just the modified function
1127            self.replace_modified_functions(&visitor.modified_function)?;
1128            Ok(ModificationResult {
1129                changed: true,
1130                modified_nodes: vec![backup_node],
1131            })
1132        } else {
1133            anyhow::bail!("Pattern '{}' not found in any match expression", op.pattern)
1134        }
1135    }
1136
1137    pub(crate) fn add_impl_method(&mut self, op: &AddImplMethodOp) -> Result<ModificationResult> {
1138        // Parse the method definition
1139        let method_code = format!("impl Dummy {{ {} }}", op.method_def);
1140        let dummy: syn::ItemImpl = parse_str(&method_code)
1141            .context("Failed to parse method definition")?;
1142
1143        let new_method = dummy.items.first()
1144            .context("No method found in definition")?
1145            .clone();
1146
1147        // Get the method name for idempotency check
1148        let method_name = match &new_method {
1149            syn::ImplItem::Fn(f) => f.sig.ident.to_string(),
1150            _ => anyhow::bail!("Only method definitions are supported"),
1151        };
1152
1153        // Find the impl block
1154        let impl_index = self.syntax_tree.items.iter().position(|item| {
1155            if let Item::Impl(impl_block) = item {
1156                // Check if this is the right impl block
1157                if let syn::Type::Path(type_path) = &*impl_block.self_ty {
1158                    if let Some(segment) = type_path.path.segments.last() {
1159                        return segment.ident == op.target;
1160                    }
1161                }
1162            }
1163            false
1164        }).ok_or_else(|| anyhow::anyhow!("impl block for '{}' not found", op.target))?;
1165
1166        // Check if method already exists (idempotent)
1167        let impl_block = match &self.syntax_tree.items[impl_index] {
1168            Item::Impl(i) => i,
1169            _ => unreachable!(),
1170        };
1171
1172        let method_exists = impl_block.items.iter().any(|item| {
1173            if let syn::ImplItem::Fn(f) = item {
1174                f.sig.ident == method_name
1175            } else {
1176                false
1177            }
1178        });
1179
1180        if method_exists {
1181            return Ok(ModificationResult {
1182                changed: false,
1183                modified_nodes: vec![],
1184            });
1185        }
1186
1187        // Create backup of original impl block before modification
1188        let backup_node = BackupNode {
1189            node_type: "ItemImpl".to_string(),
1190            identifier: op.target.clone(),
1191            original_content: self.unparse_item(&self.syntax_tree.items[impl_index].clone()),
1192            location: self.span_to_location(impl_block.span()),
1193        };
1194
1195        // Get the span before modification
1196        let impl_span = impl_block.span();
1197
1198        // Add the method to the impl block
1199        match &mut self.syntax_tree.items[impl_index] {
1200            Item::Impl(impl_block) => {
1201                // Add based on position
1202                match &op.position {
1203                    InsertPosition::First => {
1204                        impl_block.items.insert(0, new_method);
1205                    }
1206                    InsertPosition::Last => {
1207                        impl_block.items.push(new_method);
1208                    }
1209                    InsertPosition::After(name) => {
1210                        let pos = impl_block.items.iter().position(|item| {
1211                            if let syn::ImplItem::Fn(f) = item {
1212                                f.sig.ident == name
1213                            } else {
1214                                false
1215                            }
1216                        }).with_context(|| format!("Method '{}' not found", name))?;
1217                        impl_block.items.insert(pos + 1, new_method);
1218                    }
1219                    InsertPosition::Before(name) => {
1220                        let pos = impl_block.items.iter().position(|item| {
1221                            if let syn::ImplItem::Fn(f) = item {
1222                                f.sig.ident == name
1223                            } else {
1224                                false
1225                            }
1226                        }).with_context(|| format!("Method '{}' not found", name))?;
1227                        impl_block.items.insert(pos, new_method);
1228                    }
1229                }
1230            }
1231            _ => unreachable!(),
1232        }
1233
1234        // Use prettyplease to format just this impl block
1235        self.replace_formatted_item(impl_index, impl_span)?;
1236
1237        Ok(ModificationResult {
1238            changed: true,
1239            modified_nodes: vec![backup_node],
1240        })
1241    }
1242
1243    pub(crate) fn add_use_statement(&mut self, op: &AddUseStatementOp) -> Result<ModificationResult> {
1244        // Parse the use statement
1245        let use_code = format!("use {};", op.use_path);
1246        let use_item: syn::ItemUse = parse_str(&use_code)
1247            .context("Failed to parse use statement")?;
1248
1249        // Check if this use statement already exists (idempotent)
1250        let use_exists = self.syntax_tree.items.iter().any(|item| {
1251            if let Item::Use(existing_use) = item {
1252                // Compare the use trees
1253                existing_use.tree.to_token_stream().to_string() ==
1254                    use_item.tree.to_token_stream().to_string()
1255            } else {
1256                false
1257            }
1258        });
1259
1260        if use_exists {
1261            return Ok(ModificationResult {
1262                changed: false,
1263                modified_nodes: vec![],
1264            });
1265        }
1266
1267        // Create a simple backup for use statements (track by line position)
1268        let backup_node = BackupNode {
1269            node_type: "ItemUse".to_string(),
1270            identifier: op.use_path.clone(),
1271            original_content: format!("use {};", op.use_path),
1272            location: NodeLocation {
1273                line: 0,
1274                column: 0,
1275                end_line: 0,
1276                end_column: 0,
1277            },
1278        };
1279
1280        // Find the position to insert the use statement
1281        let insert_index = match &op.position {
1282            InsertPosition::First => 0,
1283            InsertPosition::Last => {
1284                // Find the last use statement
1285                self.syntax_tree.items.iter()
1286                    .rposition(|item| matches!(item, Item::Use(_)))
1287                    .map(|i| i + 1)
1288                    .unwrap_or(0)
1289            }
1290            InsertPosition::After(path) => {
1291                // Find the use statement matching the path
1292                let pos = self.syntax_tree.items.iter().position(|item| {
1293                    if let Item::Use(u) = item {
1294                        u.tree.to_token_stream().to_string().contains(path)
1295                    } else {
1296                        false
1297                    }
1298                }).with_context(|| format!("Use statement for '{}' not found", path))?;
1299                pos + 1
1300            }
1301            InsertPosition::Before(path) => {
1302                // Find the use statement matching the path
1303                self.syntax_tree.items.iter().position(|item| {
1304                    if let Item::Use(u) = item {
1305                        u.tree.to_token_stream().to_string().contains(path)
1306                    } else {
1307                        false
1308                    }
1309                }).with_context(|| format!("Use statement for '{}' not found", path))?
1310            }
1311        };
1312
1313        // Insert the use statement into the AST
1314        self.syntax_tree.items.insert(insert_index, Item::Use(use_item));
1315
1316        // Find the byte position in the source where we need to insert
1317        // We want to insert at the beginning of a line
1318        let insert_line_pos = if insert_index == 0 {
1319            // Insert at very beginning
1320            0
1321        } else {
1322            // Insert after the previous item
1323            let prev_item = &self.syntax_tree.items[insert_index - 1];
1324            let span = prev_item.span();
1325            let end_pos = self.span_to_byte_offset(span.end());
1326
1327            // Find the end of this line (where the newline is)
1328            let mut line_end = end_pos;
1329            while line_end < self.content.len() && self.content.as_bytes()[line_end] != b'\n' {
1330                line_end += 1;
1331            }
1332            // Move past the newline to the start of the next line
1333            if line_end < self.content.len() {
1334                line_end + 1
1335            } else {
1336                // At end of file, add a newline first
1337                self.content.push('\n');
1338                self.content.len()
1339            }
1340        };
1341
1342        // Format the use statement
1343        let use_str = format!("use {};\n", op.use_path);
1344
1345        // Insert the use statement
1346        self.content.insert_str(insert_line_pos, &use_str);
1347
1348        Ok(ModificationResult {
1349            changed: true,
1350            modified_nodes: vec![backup_node],
1351        })
1352    }
1353
1354    pub(crate) fn add_derive(&mut self, op: &AddDeriveOp) -> Result<ModificationResult> {
1355        // Find the target item (struct or enum)
1356        let item_index = self.syntax_tree.items.iter().position(|item| {
1357            match (&op.target_type as &str, item) {
1358                ("struct", Item::Struct(s)) => s.ident == op.target_name,
1359                ("enum", Item::Enum(e)) => e.ident == op.target_name,
1360                _ => false,
1361            }
1362        }).ok_or_else(|| anyhow::anyhow!("{} '{}' not found", op.target_type, op.target_name))?;
1363
1364        // Get the item and check for existing derives
1365        let (existing_derives, item_span, item_attrs) = match &self.syntax_tree.items[item_index] {
1366            Item::Struct(s) => (Self::extract_derives(&s.attrs), s.span(), &s.attrs),
1367            Item::Enum(e) => (Self::extract_derives(&e.attrs), e.span(), &e.attrs),
1368            _ => (Vec::new(), proc_macro2::Span::call_site(), &Vec::new() as &Vec<syn::Attribute>),
1369        };
1370
1371        // Check if the item matches the where filter (if specified)
1372        if let Some(ref where_filter) = op.where_filter {
1373            if !self.matches_where_filter(item_attrs, where_filter)? {
1374                // Item doesn't match filter - skip without error
1375                return Ok(ModificationResult {
1376                    changed: false,
1377                    modified_nodes: vec![],
1378                });
1379            }
1380        }
1381
1382        // Create backup of original item before modification
1383        let backup_node = BackupNode {
1384            node_type: if op.target_type == "struct" { "ItemStruct" } else { "ItemEnum" }.to_string(),
1385            identifier: op.target_name.clone(),
1386            original_content: self.unparse_item(&self.syntax_tree.items[item_index].clone()),
1387            location: self.span_to_location(item_span),
1388        };
1389
1390        // Filter out derives that already exist (idempotent)
1391        let new_derives: Vec<String> = op.derives.iter()
1392            .filter(|d| !existing_derives.contains(&d.to_string()))
1393            .cloned()
1394            .collect();
1395
1396        if new_derives.is_empty() {
1397            // All derives already exist
1398            return Ok(ModificationResult {
1399                changed: false,
1400                modified_nodes: vec![],
1401            });
1402        }
1403
1404        // Combine existing and new derives
1405        let mut all_derives = existing_derives;
1406        all_derives.extend(new_derives);
1407
1408        // Convert to string refs for the update function
1409        let all_derives_refs: Vec<&str> = all_derives.iter().map(|s| s.as_str()).collect();
1410
1411        // Update the AST item's attributes
1412        match &mut self.syntax_tree.items[item_index] {
1413            Item::Struct(s) => {
1414                Self::update_derive_attr(&mut s.attrs, &all_derives_refs)?;
1415            }
1416            Item::Enum(e) => {
1417                Self::update_derive_attr(&mut e.attrs, &all_derives_refs)?;
1418            }
1419            _ => unreachable!(),
1420        }
1421
1422        // Use prettyplease to format just this item
1423        self.replace_formatted_item(item_index, item_span)?;
1424
1425        Ok(ModificationResult {
1426            changed: true,
1427            modified_nodes: vec![backup_node],
1428        })
1429    }
1430
1431    /// Replace an item in the content with a formatted version
1432    fn replace_formatted_item(&mut self, item_index: usize, original_span: Span) -> Result<()> {
1433        // Get the item start and end positions from the original source
1434        let item_start_pos = self.span_to_byte_offset(original_span.start());
1435        let item_end_pos = self.span_to_byte_offset(original_span.end());
1436
1437        // Find the actual start (including attributes)
1438        let mut actual_start = item_start_pos;
1439
1440        // Search backwards for attributes
1441        let mut temp_pos = item_start_pos;
1442        while temp_pos > 0 {
1443            // Move to previous line
1444            temp_pos = temp_pos.saturating_sub(1);
1445            let mut line_start = temp_pos;
1446            while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1447                line_start -= 1;
1448            }
1449
1450            let line = if temp_pos < self.content.len() {
1451                &self.content[line_start..temp_pos + 1]
1452            } else {
1453                &self.content[line_start..]
1454            };
1455            let trimmed = line.trim();
1456
1457            if trimmed.starts_with("#[") {
1458                actual_start = line_start;
1459                temp_pos = line_start;
1460            } else if trimmed.is_empty() {
1461                temp_pos = line_start;
1462            } else {
1463                break;
1464            }
1465
1466            if line_start == 0 {
1467                break;
1468            }
1469        }
1470
1471        // Create a temporary file with just this item for pretty formatting
1472        let item_clone = self.syntax_tree.items[item_index].clone();
1473        let temp_file = syn::File {
1474            shebang: None,
1475            attrs: Vec::new(),
1476            items: vec![item_clone],
1477        };
1478
1479        // Format the item using prettyplease
1480        let formatted = prettyplease::unparse(&temp_file);
1481        let formatted = formatted.trim();
1482
1483        // Replace in content
1484        self.content.replace_range(actual_start..item_end_pos, formatted);
1485
1486        Ok(())
1487    }
1488
1489    /// Extract existing derive traits from attributes - returns owned Strings
1490    fn extract_derives(attrs: &[syn::Attribute]) -> Vec<String> {
1491        for attr in attrs {
1492            if attr.path().is_ident("derive") {
1493                if let Ok(syn::Meta::List(meta_list)) = attr.meta.clone().try_into() {
1494                    let tokens_str = meta_list.tokens.to_string();
1495                    return tokens_str
1496                        .split(',')
1497                        .map(|s| s.trim().to_string())
1498                        .collect();
1499                }
1500            }
1501        }
1502        Vec::new()
1503    }
1504
1505    /// Check if an item matches the where filter criteria
1506    /// Supports filters like:
1507    /// - "derives_trait:Clone" - matches if item derives Clone
1508    /// - "derives_trait:Clone,Debug" - matches if item derives Clone OR Debug
1509    fn matches_where_filter(&self, attrs: &[syn::Attribute], where_filter: &str) -> Result<bool> {
1510        // Parse the filter: "derives_trait:Clone,Debug"
1511        if let Some(filter_value) = where_filter.strip_prefix("derives_trait:") {
1512            let required_traits: Vec<&str> = filter_value.split(',').map(|s| s.trim()).collect();
1513            let existing_derives = Self::extract_derives(attrs);
1514
1515            // Check if ANY of the required traits are present
1516            for required_trait in required_traits {
1517                if existing_derives.iter().any(|d| d == required_trait) {
1518                    return Ok(true);
1519                }
1520            }
1521            return Ok(false);
1522        }
1523
1524        // Unknown filter type - default to match (don't break existing behavior)
1525        Ok(true)
1526    }
1527
1528    /// Update or create derive attribute in the attribute list
1529    fn update_derive_attr(attrs: &mut Vec<syn::Attribute>, derives: &[&str]) -> Result<()> {
1530        let derive_str = derives.join(", ");
1531
1532        // Parse a dummy struct with the derive to extract the attribute
1533        let dummy = format!("#[derive({})]\nstruct Dummy;", derive_str);
1534        let parsed: syn::ItemStruct = parse_str(&dummy)
1535            .context("Failed to parse derive attribute")?;
1536
1537        let new_attr = parsed.attrs.into_iter()
1538            .find(|a| a.path().is_ident("derive"))
1539            .context("Failed to extract derive attribute")?;
1540
1541        // Find existing derive attribute and replace it
1542        if let Some(pos) = attrs.iter().position(|a| a.path().is_ident("derive")) {
1543            attrs[pos] = new_attr;
1544        } else {
1545            // Add new derive attribute at the beginning
1546            attrs.insert(0, new_attr);
1547        }
1548
1549        Ok(())
1550    }
1551
1552    /// Replace the modified function(s) in the content with formatted versions
1553    fn replace_modified_functions(&mut self, modified_function: &Option<String>) -> Result<()> {
1554        // If no specific function was targeted, format the entire file
1555        if modified_function.is_none() {
1556            self.content = prettyplease::unparse(&self.syntax_tree);
1557            return Ok(());
1558        }
1559
1560        // Parse the ORIGINAL content to get the correct spans
1561        let original_syntax_tree: File = syn::parse_str(&self.content)
1562            .context("Failed to re-parse original content")?;
1563
1564        let function_name = modified_function.as_ref().unwrap();
1565
1566        // Find the function in the ORIGINAL syntax tree to get correct byte positions
1567        let original_fn = original_syntax_tree.items.iter()
1568            .find_map(|item| {
1569                if let Item::Fn(f) = item {
1570                    if f.sig.ident == function_name {
1571                        return Some(f.clone());
1572                    }
1573                }
1574                None
1575            })
1576            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in original", function_name))?;
1577
1578        // Get the span of the original function (these are the correct byte positions)
1579        let start = self.span_to_byte_offset(original_fn.span().start());
1580        let end = self.span_to_byte_offset(original_fn.span().end());
1581
1582        // Find the MODIFIED function in the modified syntax tree
1583        let modified_fn = self.syntax_tree.items.iter()
1584            .find_map(|item| {
1585                if let Item::Fn(f) = item {
1586                    if f.sig.ident == function_name {
1587                        return Some(f.clone());
1588                    }
1589                }
1590                None
1591            })
1592            .ok_or_else(|| anyhow::anyhow!("Function '{}' not found in modified AST", function_name))?;
1593
1594        // Format just the modified function using prettyplease
1595        let dummy_file = syn::File {
1596            shebang: None,
1597            attrs: Vec::new(),
1598            items: vec![Item::Fn(modified_fn)],
1599        };
1600
1601        let formatted_fn = prettyplease::unparse(&dummy_file);
1602
1603        // Extract just the function (remove any extra newlines at start/end)
1604        let formatted_fn = formatted_fn.trim();
1605
1606        // Replace the function in the original content using original spans
1607        self.content.replace_range(start..end, formatted_fn);
1608
1609        Ok(())
1610    }
1611    
1612    fn span_to_byte_offset(&self, pos: LineColumn) -> usize {
1613        let line_idx = pos.line.saturating_sub(1);
1614        if line_idx < self.line_offsets.len() {
1615            self.line_offsets[line_idx] + pos.column
1616        } else {
1617            self.content.len()
1618        }
1619    }
1620    
1621    fn find_after_field_end(&self, pos: usize) -> usize {
1622        // Look for comma or newline after the field
1623        let mut i = pos;
1624        while i < self.content.len() {
1625            match self.content.as_bytes()[i] as char {
1626                ',' => return i + 1,
1627                '\n' => return i + 1,
1628                _ => i += 1,
1629            }
1630        }
1631        pos
1632    }
1633    
1634    fn get_indentation(&self, pos: usize) -> String {
1635        // Find the start of the current line
1636        let mut line_start = pos;
1637        while line_start > 0 && self.content.as_bytes()[line_start - 1] != b'\n' {
1638            line_start -= 1;
1639        }
1640        
1641        // Count spaces/tabs at the start of the line
1642        let mut indent = String::new();
1643        let mut i = line_start;
1644        while i < self.content.len() {
1645            match self.content.as_bytes()[i] as char {
1646                ' ' | '\t' => {
1647                    indent.push(self.content.as_bytes()[i] as char);
1648                    i += 1;
1649                }
1650                _ => break,
1651            }
1652        }
1653        
1654        // If we're inserting in an empty struct/enum, add default indentation
1655        if indent.is_empty() {
1656            "    ".to_string()
1657        } else {
1658            indent
1659        }
1660    }
1661    
1662    pub fn to_string(&self) -> String {
1663        self.content.clone()
1664    }
1665
1666    /// Inspect and list AST nodes (e.g., struct literals) in the file
1667    pub(crate) fn inspect(&self, node_type: &str, name_filter: Option<&str>) -> Result<Vec<crate::operations::InspectResult>> {
1668        use syn::visit::Visit;
1669        use crate::operations::InspectResult;
1670
1671        let mut results = Vec::new();
1672
1673        match node_type {
1674            "struct-literal" => {
1675                // Find all struct literal expressions
1676                struct StructLiteralVisitor<'a> {
1677                    results: &'a mut Vec<InspectResult>,
1678                    name_filter: Option<&'a str>,
1679                    editor: &'a RustEditor,
1680                }
1681
1682                impl<'ast, 'a> Visit<'ast> for StructLiteralVisitor<'a> {
1683                    fn visit_expr_struct(&mut self, node: &'ast syn::ExprStruct) {
1684                        // Match based on pattern:
1685                        // - "Rectangle" → only Rectangle { ... } (no :: prefix)
1686                        // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
1687                        // - "View::Rectangle" → exact match only View::Rectangle
1688
1689                        let filter = match self.name_filter {
1690                            Some(f) => f,
1691                            None => {
1692                                // No filter - match anything
1693                                let struct_name = node.path.segments.last()
1694                                    .map(|seg| seg.ident.to_string())
1695                                    .unwrap_or_default();
1696
1697                                let snippet = self.editor.format_expr_struct(node);
1698                                let location = self.editor.span_to_location(node.span());
1699
1700                                self.results.push(InspectResult {
1701                                    file_path: String::new(),
1702                                    node_type: "ExprStruct".to_string(),
1703                                    identifier: struct_name,
1704                                    location,
1705                                    snippet,
1706                                });
1707
1708                                syn::visit::visit_expr_struct(self, node);
1709                                return;
1710                            }
1711                        };
1712
1713                        // Check if this struct literal matches the filter pattern
1714                        let matches = if filter.contains("::") {
1715                            // Pattern contains :: - check for exact or wildcard match
1716                            if filter.starts_with("*::") {
1717                                // Wildcard: *::Rectangle matches any path ending with Rectangle
1718                                let target_name = &filter[3..]; // Skip "*::"
1719                                node.path.segments.last()
1720                                    .map(|seg| seg.ident.to_string() == target_name)
1721                                    .unwrap_or(false)
1722                            } else {
1723                                // Exact path match: View::Rectangle
1724                                let path_str = node.path.segments.iter()
1725                                    .map(|seg| seg.ident.to_string())
1726                                    .collect::<Vec<_>>()
1727                                    .join("::");
1728                                path_str == filter
1729                            }
1730                        } else {
1731                            // No :: in pattern - only match pure struct literals (no path qualifier)
1732                            node.path.get_ident()
1733                                .map(|ident| ident.to_string() == filter)
1734                                .unwrap_or(false)
1735                        };
1736
1737                        if !matches {
1738                            syn::visit::visit_expr_struct(self, node);
1739                            return;
1740                        }
1741
1742                        // Get the struct name for the identifier
1743                        let struct_name = node.path.segments.last()
1744                            .map(|seg| seg.ident.to_string())
1745                            .unwrap_or_default();
1746
1747                        // Format the struct literal
1748                        let snippet = self.editor.format_expr_struct(node);
1749                        let location = self.editor.span_to_location(node.span());
1750
1751                        self.results.push(InspectResult {
1752                            file_path: String::new(), // Will be filled in by caller
1753                            node_type: "ExprStruct".to_string(),
1754                            identifier: struct_name,
1755                            location,
1756                            snippet,
1757                        });
1758
1759                        // Continue visiting nested expressions
1760                        syn::visit::visit_expr_struct(self, node);
1761                    }
1762                }
1763
1764                let mut visitor = StructLiteralVisitor {
1765                    results: &mut results,
1766                    name_filter,
1767                    editor: self,
1768                };
1769
1770                // Visit all items in the file
1771                for item in &self.syntax_tree.items {
1772                    syn::visit::visit_item(&mut visitor, item);
1773                }
1774            }
1775            "match-arm" => {
1776                // Find all match arms
1777                struct MatchArmVisitor<'a> {
1778                    results: &'a mut Vec<InspectResult>,
1779                    pattern_filter: Option<&'a str>,
1780                    editor: &'a RustEditor,
1781                }
1782
1783                impl<'ast, 'a> Visit<'ast> for MatchArmVisitor<'a> {
1784                    fn visit_expr_match(&mut self, node: &'ast syn::ExprMatch) {
1785                        // Iterate through all arms in this match expression
1786                        for arm in &node.arms {
1787                            // Convert pattern to string for matching
1788                            let pat = &arm.pat;
1789                            let pattern_str = quote::quote!(#pat).to_string();
1790
1791                            // Apply pattern filter if specified
1792                            if let Some(filter) = self.pattern_filter {
1793                                // Normalize both for comparison (remove spaces)
1794                                let normalized_pattern = pattern_str.replace(" ", "");
1795                                let normalized_filter = filter.replace(" ", "");
1796
1797                                if !normalized_pattern.contains(&normalized_filter) {
1798                                    continue;
1799                                }
1800                            }
1801
1802                            // Format the match arm (pattern => body)
1803                            let snippet = self.editor.format_match_arm(arm);
1804                            let location = self.editor.span_to_location(arm.span());
1805
1806                            self.results.push(InspectResult {
1807                                file_path: String::new(), // Will be filled in by caller
1808                                node_type: "MatchArm".to_string(),
1809                                identifier: pattern_str.replace(" ", ""),
1810                                location,
1811                                snippet,
1812                            });
1813                        }
1814
1815                        // Continue visiting nested expressions
1816                        syn::visit::visit_expr_match(self, node);
1817                    }
1818                }
1819
1820                let mut visitor = MatchArmVisitor {
1821                    results: &mut results,
1822                    pattern_filter: name_filter,
1823                    editor: self,
1824                };
1825
1826                // Visit all items in the file
1827                for item in &self.syntax_tree.items {
1828                    syn::visit::visit_item(&mut visitor, item);
1829                }
1830            }
1831            "enum-usage" => {
1832                // Find all enum variant usages (paths like Operator::Error)
1833                struct EnumUsageVisitor<'a> {
1834                    results: &'a mut Vec<InspectResult>,
1835                    path_filter: Option<&'a str>,
1836                    editor: &'a RustEditor,
1837                }
1838
1839                impl<'ast, 'a> Visit<'ast> for EnumUsageVisitor<'a> {
1840                    fn visit_expr_path(&mut self, node: &'ast syn::ExprPath) {
1841                        // Convert path to string
1842                        let path = &node.path;
1843                        let path_str = quote::quote!(#path).to_string();
1844
1845                        // Apply path filter if specified
1846                        if let Some(filter) = self.path_filter {
1847                            // Normalize both for comparison (remove spaces)
1848                            let normalized_path = path_str.replace(" ", "");
1849                            let normalized_filter = filter.replace(" ", "");
1850
1851                            if !normalized_path.contains(&normalized_filter) {
1852                                syn::visit::visit_expr_path(self, node);
1853                                return;
1854                            }
1855                        }
1856
1857                        // Format the path expression
1858                        let snippet = self.editor.format_expr_path(node);
1859                        let location = self.editor.span_to_location(node.span());
1860
1861                        self.results.push(InspectResult {
1862                            file_path: String::new(), // Will be filled in by caller
1863                            node_type: "ExprPath".to_string(),
1864                            identifier: path_str.replace(" ", ""),
1865                            location,
1866                            snippet,
1867                        });
1868
1869                        // Continue visiting nested expressions
1870                        syn::visit::visit_expr_path(self, node);
1871                    }
1872                }
1873
1874                let mut visitor = EnumUsageVisitor {
1875                    results: &mut results,
1876                    path_filter: name_filter,
1877                    editor: self,
1878                };
1879
1880                // Visit all items in the file
1881                for item in &self.syntax_tree.items {
1882                    syn::visit::visit_item(&mut visitor, item);
1883                }
1884            }
1885            "function-call" => {
1886                // Find all function call expressions
1887                struct FunctionCallVisitor<'a> {
1888                    results: &'a mut Vec<InspectResult>,
1889                    name_filter: Option<&'a str>,
1890                    editor: &'a RustEditor,
1891                }
1892
1893                impl<'ast, 'a> Visit<'ast> for FunctionCallVisitor<'a> {
1894                    fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
1895                        // Extract function name from the call expression
1896                        let func_name = if let syn::Expr::Path(expr_path) = &*node.func {
1897                            // Get the last segment of the path as the function name
1898                            expr_path.path.segments.last()
1899                                .map(|seg| seg.ident.to_string())
1900                                .unwrap_or_default()
1901                        } else {
1902                            // For other expression types, use quote to convert to string
1903                            quote::quote!(#node.func).to_string()
1904                        };
1905
1906                        // Apply name filter if specified
1907                        if let Some(filter) = self.name_filter {
1908                            if func_name != filter {
1909                                syn::visit::visit_expr_call(self, node);
1910                                return;
1911                            }
1912                        }
1913
1914                        // Format the function call
1915                        let snippet = self.editor.format_expr_call(node);
1916                        let location = self.editor.span_to_location(node.span());
1917
1918                        self.results.push(InspectResult {
1919                            file_path: String::new(), // Will be filled in by caller
1920                            node_type: "ExprCall".to_string(),
1921                            identifier: func_name,
1922                            location,
1923                            snippet,
1924                        });
1925
1926                        // Continue visiting nested expressions
1927                        syn::visit::visit_expr_call(self, node);
1928                    }
1929                }
1930
1931                let mut visitor = FunctionCallVisitor {
1932                    results: &mut results,
1933                    name_filter,
1934                    editor: self,
1935                };
1936
1937                // Visit all items in the file
1938                for item in &self.syntax_tree.items {
1939                    syn::visit::visit_item(&mut visitor, item);
1940                }
1941            }
1942            "method-call" => {
1943                // Find all method call expressions
1944                struct MethodCallVisitor<'a> {
1945                    results: &'a mut Vec<InspectResult>,
1946                    name_filter: Option<&'a str>,
1947                    editor: &'a RustEditor,
1948                }
1949
1950                impl<'ast, 'a> Visit<'ast> for MethodCallVisitor<'a> {
1951                    fn visit_expr_method_call(&mut self, node: &'ast syn::ExprMethodCall) {
1952                        // Extract method name
1953                        let method_name = node.method.to_string();
1954
1955                        // Apply name filter if specified
1956                        if let Some(filter) = self.name_filter {
1957                            if method_name != filter {
1958                                syn::visit::visit_expr_method_call(self, node);
1959                                return;
1960                            }
1961                        }
1962
1963                        // Format the method call
1964                        let snippet = self.editor.format_expr_method_call(node);
1965                        let location = self.editor.span_to_location(node.span());
1966
1967                        self.results.push(InspectResult {
1968                            file_path: String::new(), // Will be filled in by caller
1969                            node_type: "ExprMethodCall".to_string(),
1970                            identifier: method_name,
1971                            location,
1972                            snippet,
1973                        });
1974
1975                        // Continue visiting nested expressions
1976                        syn::visit::visit_expr_method_call(self, node);
1977                    }
1978                }
1979
1980                let mut visitor = MethodCallVisitor {
1981                    results: &mut results,
1982                    name_filter,
1983                    editor: self,
1984                };
1985
1986                // Visit all items in the file
1987                for item in &self.syntax_tree.items {
1988                    syn::visit::visit_item(&mut visitor, item);
1989                }
1990            }
1991            "identifier" => {
1992                // Find all identifier references
1993                struct IdentifierVisitor<'a> {
1994                    results: &'a mut Vec<InspectResult>,
1995                    name_filter: Option<&'a str>,
1996                    editor: &'a RustEditor,
1997                }
1998
1999                impl<'ast, 'a> Visit<'ast> for IdentifierVisitor<'a> {
2000                    fn visit_ident(&mut self, node: &'ast syn::Ident) {
2001                        // Extract identifier name
2002                        let ident_name = node.to_string();
2003
2004                        // Apply name filter if specified
2005                        if let Some(filter) = self.name_filter {
2006                            if ident_name != filter {
2007                                syn::visit::visit_ident(self, node);
2008                                return;
2009                            }
2010                        }
2011
2012                        // Format the identifier
2013                        let snippet = self.editor.format_ident(node);
2014                        let location = self.editor.span_to_location(node.span());
2015
2016                        self.results.push(InspectResult {
2017                            file_path: String::new(), // Will be filled in by caller
2018                            node_type: "Ident".to_string(),
2019                            identifier: ident_name,
2020                            location,
2021                            snippet,
2022                        });
2023
2024                        // Continue visiting
2025                        syn::visit::visit_ident(self, node);
2026                    }
2027                }
2028
2029                let mut visitor = IdentifierVisitor {
2030                    results: &mut results,
2031                    name_filter,
2032                    editor: self,
2033                };
2034
2035                // Visit all items in the file
2036                for item in &self.syntax_tree.items {
2037                    syn::visit::visit_item(&mut visitor, item);
2038                }
2039            }
2040            "type-ref" => {
2041                // Find all type path usages
2042                struct TypeRefVisitor<'a> {
2043                    results: &'a mut Vec<InspectResult>,
2044                    name_filter: Option<&'a str>,
2045                    editor: &'a RustEditor,
2046                }
2047
2048                impl<'ast, 'a> Visit<'ast> for TypeRefVisitor<'a> {
2049                    fn visit_type_path(&mut self, node: &'ast syn::TypePath) {
2050                        // Extract type name (last segment of path)
2051                        let type_name = node.path.segments.last()
2052                            .map(|seg| seg.ident.to_string())
2053                            .unwrap_or_default();
2054
2055                        // Apply name filter if specified
2056                        if let Some(filter) = self.name_filter {
2057                            if type_name != filter {
2058                                syn::visit::visit_type_path(self, node);
2059                                return;
2060                            }
2061                        }
2062
2063                        // Format the type path
2064                        let snippet = self.editor.format_type_path(node);
2065                        let location = self.editor.span_to_location(node.span());
2066
2067                        // Get full path for identifier
2068                        let path = &node.path;
2069                        let path_str = quote::quote!(#path).to_string();
2070
2071                        self.results.push(InspectResult {
2072                            file_path: String::new(), // Will be filled in by caller
2073                            node_type: "TypePath".to_string(),
2074                            identifier: path_str.replace(" ", ""),
2075                            location,
2076                            snippet,
2077                        });
2078
2079                        // Continue visiting
2080                        syn::visit::visit_type_path(self, node);
2081                    }
2082                }
2083
2084                let mut visitor = TypeRefVisitor {
2085                    results: &mut results,
2086                    name_filter,
2087                    editor: self,
2088                };
2089
2090                // Visit all items in the file
2091                for item in &self.syntax_tree.items {
2092                    syn::visit::visit_item(&mut visitor, item);
2093                }
2094            }
2095            "macro-call" => {
2096                // Find all macro call expressions
2097                struct MacroCallVisitor<'a> {
2098                    results: &'a mut Vec<InspectResult>,
2099                    name_filter: Option<&'a str>,
2100                    editor: &'a RustEditor,
2101                }
2102
2103                impl<'ast, 'a> Visit<'ast> for MacroCallVisitor<'a> {
2104                    fn visit_expr_macro(&mut self, node: &'ast syn::ExprMacro) {
2105                        // Extract macro name from the path
2106                        let macro_name = node.mac.path.segments.last()
2107                            .map(|seg| seg.ident.to_string())
2108                            .unwrap_or_default();
2109
2110                        // Apply name filter if specified
2111                        if let Some(filter) = self.name_filter {
2112                            if macro_name != filter {
2113                                syn::visit::visit_expr_macro(self, node);
2114                                return;
2115                            }
2116                        }
2117
2118                        // Format the macro call
2119                        let snippet = self.editor.format_expr_macro(node);
2120                        let location = self.editor.span_to_location(node.span());
2121
2122                        self.results.push(InspectResult {
2123                            file_path: String::new(), // Will be filled in by caller
2124                            node_type: "ExprMacro".to_string(),
2125                            identifier: macro_name,
2126                            location,
2127                            snippet,
2128                        });
2129
2130                        // Continue visiting nested expressions
2131                        syn::visit::visit_expr_macro(self, node);
2132                    }
2133
2134                    fn visit_stmt(&mut self, node: &'ast syn::Stmt) {
2135                        // Also catch macro calls at statement level (e.g., println! as statement)
2136                        if let syn::Stmt::Macro(macro_stmt) = node {
2137                            let macro_name = macro_stmt.mac.path.segments.last()
2138                                .map(|seg| seg.ident.to_string())
2139                                .unwrap_or_default();
2140
2141                            // Apply name filter if specified
2142                            if let Some(filter) = self.name_filter {
2143                                if macro_name != filter {
2144                                    syn::visit::visit_stmt(self, node);
2145                                    return;
2146                                }
2147                            }
2148
2149                            // Format the macro call
2150                            let snippet = self.editor.format_stmt_macro(macro_stmt);
2151                            let location = self.editor.span_to_location(macro_stmt.span());
2152
2153                            self.results.push(InspectResult {
2154                                file_path: String::new(), // Will be filled in by caller
2155                                node_type: "StmtMacro".to_string(),
2156                                identifier: macro_name,
2157                                location,
2158                                snippet,
2159                            });
2160                        }
2161
2162                        // Continue visiting
2163                        syn::visit::visit_stmt(self, node);
2164                    }
2165                }
2166
2167                let mut visitor = MacroCallVisitor {
2168                    results: &mut results,
2169                    name_filter,
2170                    editor: self,
2171                };
2172
2173                // Visit all items in the file
2174                for item in &self.syntax_tree.items {
2175                    syn::visit::visit_item(&mut visitor, item);
2176                }
2177            }
2178            _ => anyhow::bail!("Unsupported node type: {}", node_type),
2179        }
2180
2181        Ok(results)
2182    }
2183
2184    /// Format an ExprStruct node as a string - extracts original source
2185    fn format_expr_struct(&self, expr: &syn::ExprStruct) -> String {
2186        // Extract the original source code from the file content using the span
2187        let start = self.span_to_byte_offset(expr.span().start());
2188        let end = self.span_to_byte_offset(expr.span().end());
2189
2190        // Get the original text and collapse to single line
2191        let original = &self.content[start..end];
2192
2193        // Replace multiple whitespace/newlines with single space for single-line format
2194        original.split_whitespace().collect::<Vec<_>>().join(" ")
2195    }
2196
2197    /// Format a match arm as a string - extracts original source
2198    fn format_match_arm(&self, arm: &syn::Arm) -> String {
2199        // Extract the original source code from the file content using the span
2200        let start = self.span_to_byte_offset(arm.span().start());
2201        let end = self.span_to_byte_offset(arm.span().end());
2202
2203        // Get the original text and collapse to single line
2204        let original = &self.content[start..end];
2205
2206        // Replace multiple whitespace/newlines with single space for single-line format
2207        original.split_whitespace().collect::<Vec<_>>().join(" ")
2208    }
2209
2210    /// Format an ExprPath node as a string - extracts original source
2211    fn format_expr_path(&self, expr: &syn::ExprPath) -> String {
2212        // Extract the original source code from the file content using the span
2213        let start = self.span_to_byte_offset(expr.span().start());
2214        let end = self.span_to_byte_offset(expr.span().end());
2215
2216        // Get the original text and collapse to single line
2217        let original = &self.content[start..end];
2218
2219        // Replace multiple whitespace/newlines with single space for single-line format
2220        original.split_whitespace().collect::<Vec<_>>().join(" ")
2221    }
2222
2223    /// Format an ExprCall node as a string - extracts original source
2224    fn format_expr_call(&self, expr: &syn::ExprCall) -> String {
2225        // Extract the original source code from the file content using the span
2226        let start = self.span_to_byte_offset(expr.span().start());
2227        let end = self.span_to_byte_offset(expr.span().end());
2228
2229        // Get the original text and collapse to single line
2230        let original = &self.content[start..end];
2231
2232        // Replace multiple whitespace/newlines with single space for single-line format
2233        original.split_whitespace().collect::<Vec<_>>().join(" ")
2234    }
2235
2236    /// Format an ExprMethodCall node as a string - extracts original source
2237    fn format_expr_method_call(&self, expr: &syn::ExprMethodCall) -> String {
2238        // Extract the original source code from the file content using the span
2239        let start = self.span_to_byte_offset(expr.span().start());
2240        let end = self.span_to_byte_offset(expr.span().end());
2241
2242        // Get the original text and collapse to single line
2243        let original = &self.content[start..end];
2244
2245        // Replace multiple whitespace/newlines with single space for single-line format
2246        original.split_whitespace().collect::<Vec<_>>().join(" ")
2247    }
2248
2249    /// Format an Ident node as a string - just return the identifier
2250    fn format_ident(&self, ident: &syn::Ident) -> String {
2251        ident.to_string()
2252    }
2253
2254    /// Format a TypePath node as a string - extracts original source
2255    fn format_type_path(&self, ty: &syn::TypePath) -> String {
2256        // Extract the original source code from the file content using the span
2257        let start = self.span_to_byte_offset(ty.span().start());
2258        let end = self.span_to_byte_offset(ty.span().end());
2259
2260        // Get the original text and collapse to single line
2261        let original = &self.content[start..end];
2262
2263        // Replace multiple whitespace/newlines with single space for single-line format
2264        original.split_whitespace().collect::<Vec<_>>().join(" ")
2265    }
2266
2267    /// Format an ExprMacro node as a string - extracts original source
2268    fn format_expr_macro(&self, expr: &syn::ExprMacro) -> String {
2269        // Extract the original source code from the file content using the span
2270        let start = self.span_to_byte_offset(expr.span().start());
2271        let end = self.span_to_byte_offset(expr.span().end());
2272
2273        // Get the original text and collapse to single line
2274        let original = &self.content[start..end];
2275
2276        // Replace multiple whitespace/newlines with single space for single-line format
2277        original.split_whitespace().collect::<Vec<_>>().join(" ")
2278    }
2279
2280    /// Format a StmtMacro node as a string - extracts original source
2281    fn format_stmt_macro(&self, stmt: &syn::StmtMacro) -> String {
2282        // Extract the original source code from the file content using the span
2283        let start = self.span_to_byte_offset(stmt.span().start());
2284        let end = self.span_to_byte_offset(stmt.span().end());
2285
2286        // Get the original text and collapse to single line
2287        let original = &self.content[start..end];
2288
2289        // Replace multiple whitespace/newlines with single space for single-line format
2290        original.split_whitespace().collect::<Vec<_>>().join(" ")
2291    }
2292
2293    /// Find the index of an item by type and name
2294    #[allow(dead_code)]
2295    pub(crate) fn find_item_index(&self, node_type: &str, name: &str) -> Result<usize> {
2296        for (index, item) in self.syntax_tree.items.iter().enumerate() {
2297            match (node_type, item) {
2298                ("struct", Item::Struct(s)) if s.ident == name => {
2299                    return Ok(index);
2300                }
2301                ("enum", Item::Enum(e)) if e.ident == name => {
2302                    return Ok(index);
2303                }
2304                ("fn", Item::Fn(f)) if f.sig.ident == name => {
2305                    return Ok(index);
2306                }
2307                ("impl", Item::Impl(impl_block)) => {
2308                    // For impl blocks, match on the self_ty
2309                    if let syn::Type::Path(type_path) = &*impl_block.self_ty {
2310                        if let Some(segment) = type_path.path.segments.last() {
2311                            if segment.ident == name {
2312                                return Ok(index);
2313                            }
2314                        }
2315                    }
2316                }
2317                _ => {}
2318            }
2319        }
2320
2321        anyhow::bail!("Item '{}' of type '{}' not found", name, node_type)
2322    }
2323
2324    /// Replace an item at a specific index with a new item
2325    #[allow(dead_code)]
2326    pub(crate) fn replace_item_at_index(&mut self, index: usize, new_item: Item) -> Result<()> {
2327        if index >= self.syntax_tree.items.len() {
2328            anyhow::bail!("Index {} out of bounds", index);
2329        }
2330
2331        // Replace the item in the syntax tree
2332        self.syntax_tree.items[index] = new_item;
2333
2334        // Reformat the entire file using prettyplease
2335        self.content = prettyplease::unparse(&self.syntax_tree);
2336
2337        // Recompute line offsets
2338        self.line_offsets = Self::compute_line_offsets(&self.content);
2339
2340        Ok(())
2341    }
2342
2343    pub fn find_node(&self, node_type: &str, name: &str) -> Result<Vec<NodeLocation>> {
2344        let mut locations = Vec::new();
2345        
2346        for item in &self.syntax_tree.items {
2347            match (node_type, item) {
2348                ("struct", Item::Struct(s)) if s.ident == name => {
2349                    locations.push(self.span_to_location(s.span()));
2350                }
2351                ("enum", Item::Enum(e)) if e.ident == name => {
2352                    locations.push(self.span_to_location(e.span()));
2353                }
2354                ("fn", Item::Fn(f)) if f.sig.ident == name => {
2355                    locations.push(self.span_to_location(f.span()));
2356                }
2357                _ => {}
2358            }
2359        }
2360        
2361        if locations.is_empty() {
2362            anyhow::bail!("Node '{}' of type '{}' not found", name, node_type);
2363        }
2364        
2365        Ok(locations)
2366    }
2367    
2368    fn span_to_location(&self, span: Span) -> NodeLocation {
2369        let start = span.start();
2370        let end = span.end();
2371
2372        NodeLocation {
2373            line: start.line,
2374            column: start.column,
2375            end_line: end.line,
2376            end_column: end.column,
2377        }
2378    }
2379
2380    /// Generic transform operation - find matching nodes and apply action
2381    pub(crate) fn transform(&mut self, op: &crate::operations::TransformOp) -> Result<ModificationResult> {
2382        use crate::operations::{InspectResult, TransformAction};
2383
2384        // First, use inspect to find all matching nodes
2385        let matches = self.inspect(&op.node_type, op.name_filter.as_deref())?;
2386
2387        // Apply content filter if specified
2388        let filtered_matches: Vec<InspectResult> = if let Some(ref content_filter) = op.content_filter {
2389            matches.into_iter()
2390                .filter(|m| m.snippet.contains(content_filter))
2391                .collect()
2392        } else {
2393            matches
2394        };
2395
2396        if filtered_matches.is_empty() {
2397            return Ok(ModificationResult {
2398                changed: false,
2399                modified_nodes: vec![],
2400            });
2401        }
2402
2403        // Now apply the transformation action to each match
2404        // We need to work backwards through the file to avoid offset issues
2405        let mut sorted_matches = filtered_matches;
2406        sorted_matches.sort_by(|a, b| {
2407            b.location.line.cmp(&a.location.line)
2408                .then(b.location.column.cmp(&a.location.column))
2409        });
2410
2411        let mut modified_nodes = Vec::new();
2412
2413        for match_result in &sorted_matches {
2414            // Create backup node
2415            let backup_node = BackupNode {
2416                node_type: match_result.node_type.clone(),
2417                identifier: match_result.identifier.clone(),
2418                original_content: match_result.snippet.clone(),
2419                location: match_result.location.clone(),
2420            };
2421
2422            // Find the byte offsets for this node
2423            let start_offset = self.line_column_to_byte_offset(
2424                match_result.location.line,
2425                match_result.location.column
2426            )?;
2427            let end_offset = self.line_column_to_byte_offset(
2428                match_result.location.end_line,
2429                match_result.location.end_column
2430            )?;
2431
2432            // Extract the original text
2433            let original_text = &self.content[start_offset..end_offset];
2434
2435            // Apply the action
2436            let replacement = match &op.action {
2437                TransformAction::Comment => {
2438                    // Comment out the code
2439                    format!("// {}", original_text.replace("\n", "\n// "))
2440                }
2441                TransformAction::Remove => {
2442                    // Remove the entire node
2443                    String::new()
2444                }
2445                TransformAction::Replace { with } => {
2446                    // Replace with provided code
2447                    with.clone()
2448                }
2449            };
2450
2451            // Replace in content
2452            self.content.replace_range(start_offset..end_offset, &replacement);
2453
2454            // Recompute line offsets after each change
2455            self.line_offsets = Self::compute_line_offsets(&self.content);
2456
2457            modified_nodes.push(backup_node);
2458        }
2459
2460        // Re-parse the content if we made changes
2461        if !modified_nodes.is_empty() {
2462            // Don't reparse for now - we're doing text-level operations
2463            // self.syntax_tree = syn::parse_str(&self.content)
2464            //     .context("Failed to re-parse content after transformation")?;
2465        }
2466
2467        Ok(ModificationResult {
2468            changed: !modified_nodes.is_empty(),
2469            modified_nodes,
2470        })
2471    }
2472
2473    /// Convert line/column to byte offset
2474    fn line_column_to_byte_offset(&self, line: usize, column: usize) -> Result<usize> {
2475        if line == 0 || line > self.line_offsets.len() {
2476            anyhow::bail!("Line {} out of range", line);
2477        }
2478
2479        let line_start = self.line_offsets[line - 1];
2480        Ok(line_start + column)
2481    }
2482}
2483
2484// Visitor for adding match arms
2485struct MatchArmAdder {
2486    target_function: Option<String>,
2487    arm_to_add: Arm,
2488    modified: bool,
2489    current_function: Option<String>,
2490    modified_function: Option<String>,
2491}
2492
2493impl VisitMut for MatchArmAdder {
2494    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2495        let prev_fn = self.current_function.clone();
2496        self.current_function = Some(node.sig.ident.to_string());
2497
2498        // Continue visiting nested items
2499        syn::visit_mut::visit_item_fn_mut(self, node);
2500
2501        self.current_function = prev_fn;
2502    }
2503
2504    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2505        // Check if we're in the right function (if specified)
2506        if let Some(ref target) = self.target_function {
2507            if self.current_function.as_ref() != Some(target) {
2508                // Continue visiting nested expressions
2509                syn::visit_mut::visit_expr_match_mut(self, node);
2510                return;
2511            }
2512        }
2513
2514        // Check if the pattern already exists (idempotent)
2515        let pattern_str = self.arm_to_add.pat.to_token_stream().to_string();
2516        let already_exists = node.arms.iter().any(|arm| {
2517            arm.pat.to_token_stream().to_string() == pattern_str
2518        });
2519
2520        if !already_exists {
2521            // Add the arm to the end
2522            node.arms.push(self.arm_to_add.clone());
2523            self.modified = true;
2524            self.modified_function = self.current_function.clone();
2525        }
2526
2527        // Continue visiting nested expressions
2528        syn::visit_mut::visit_expr_match_mut(self, node);
2529    }
2530}
2531
2532// Visitor for updating match arms
2533struct MatchArmUpdater {
2534    target_function: Option<String>,
2535    pattern_to_match: String,
2536    new_body: syn::Expr,
2537    modified: bool,
2538    current_function: Option<String>,
2539    modified_function: Option<String>,
2540}
2541
2542impl VisitMut for MatchArmUpdater {
2543    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2544        let prev_fn = self.current_function.clone();
2545        self.current_function = Some(node.sig.ident.to_string());
2546
2547        syn::visit_mut::visit_item_fn_mut(self, node);
2548
2549        self.current_function = prev_fn;
2550    }
2551
2552    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2553        // Check if we're in the right function (if specified)
2554        if let Some(ref target) = self.target_function {
2555            if self.current_function.as_ref() != Some(target) {
2556                syn::visit_mut::visit_expr_match_mut(self, node);
2557                return;
2558            }
2559        }
2560
2561        // Find and update the matching arm
2562        for arm in &mut node.arms {
2563            let pattern_str = arm.pat.to_token_stream().to_string();
2564            // Normalize whitespace for comparison
2565            let pattern_normalized = pattern_str.replace(" ", "");
2566            let target_normalized = self.pattern_to_match.replace(" ", "");
2567
2568            if pattern_normalized == target_normalized {
2569                arm.body = Box::new(self.new_body.clone());
2570                self.modified = true;
2571                self.modified_function = self.current_function.clone();
2572                break;
2573            }
2574        }
2575
2576        syn::visit_mut::visit_expr_match_mut(self, node);
2577    }
2578}
2579
2580// Visitor for removing match arms
2581struct MatchArmRemover {
2582    target_function: Option<String>,
2583    pattern_to_remove: String,
2584    modified: bool,
2585    current_function: Option<String>,
2586    modified_function: Option<String>,
2587}
2588
2589impl VisitMut for MatchArmRemover {
2590    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2591        let prev_fn = self.current_function.clone();
2592        self.current_function = Some(node.sig.ident.to_string());
2593
2594        syn::visit_mut::visit_item_fn_mut(self, node);
2595
2596        self.current_function = prev_fn;
2597    }
2598
2599    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2600        // Check if we're in the right function (if specified)
2601        if let Some(ref target) = self.target_function {
2602            if self.current_function.as_ref() != Some(target) {
2603                syn::visit_mut::visit_expr_match_mut(self, node);
2604                return;
2605            }
2606        }
2607
2608        // Find and remove the matching arm
2609        let mut index_to_remove = None;
2610        for (i, arm) in node.arms.iter().enumerate() {
2611            let pattern_str = arm.pat.to_token_stream().to_string();
2612            // Normalize whitespace for comparison
2613            let pattern_normalized = pattern_str.replace(" ", "");
2614            let target_normalized = self.pattern_to_remove.replace(" ", "");
2615
2616            if pattern_normalized == target_normalized {
2617                index_to_remove = Some(i);
2618                break;
2619            }
2620        }
2621
2622        if let Some(index) = index_to_remove {
2623            node.arms.remove(index);
2624            self.modified = true;
2625            self.modified_function = self.current_function.clone();
2626        }
2627
2628        syn::visit_mut::visit_expr_match_mut(self, node);
2629    }
2630}
2631
2632// Visitor for adding multiple match arms at once (for auto-detect)
2633struct MultiMatchArmAdder {
2634    target_function: Option<String>,
2635    arms_to_add: Vec<(String, Arm)>,  // (pattern_string, arm)
2636    modified: bool,
2637    current_function: Option<String>,
2638    modified_function: Option<String>,
2639}
2640
2641impl VisitMut for MultiMatchArmAdder {
2642    fn visit_item_fn_mut(&mut self, node: &mut syn::ItemFn) {
2643        let prev_fn = self.current_function.clone();
2644        self.current_function = Some(node.sig.ident.to_string());
2645
2646        syn::visit_mut::visit_item_fn_mut(self, node);
2647
2648        self.current_function = prev_fn;
2649    }
2650
2651    fn visit_expr_match_mut(&mut self, node: &mut ExprMatch) {
2652        // Check if we're in the right function (if specified)
2653        if let Some(ref target) = self.target_function {
2654            if self.current_function.as_ref() != Some(target) {
2655                syn::visit_mut::visit_expr_match_mut(self, node);
2656                return;
2657            }
2658        }
2659
2660        // Add all missing arms
2661        for (pattern_str, arm) in &self.arms_to_add {
2662            // Check if the pattern already exists (idempotent)
2663            let already_exists = node.arms.iter().any(|existing_arm| {
2664                existing_arm.pat.to_token_stream().to_string() == *pattern_str
2665            });
2666
2667            if !already_exists {
2668                node.arms.push(arm.clone());
2669                self.modified = true;
2670                self.modified_function = self.current_function.clone();
2671            }
2672        }
2673
2674        syn::visit_mut::visit_expr_match_mut(self, node);
2675    }
2676}
2677
2678// Visitor for adding fields to struct literal expressions
2679struct StructLiteralFieldAdder {
2680    struct_name: String,
2681    field_def: String,
2682    field_name: String,
2683    position: InsertPosition,
2684    modified: bool,
2685}
2686
2687impl VisitMut for StructLiteralFieldAdder {
2688    fn visit_expr_mut(&mut self, node: &mut Expr) {
2689        // Check if this is a struct literal expression
2690        if let Expr::Struct(expr_struct) = node {
2691            // Match based on pattern:
2692            // - "Rectangle" → only Rectangle { ... } (no :: prefix)
2693            // - "*::Rectangle" → any path ending with Rectangle (View::Rectangle, etc.)
2694            // - "View::Rectangle" → exact match only View::Rectangle
2695
2696            let is_match = if self.struct_name.contains("::") {
2697                // Pattern contains :: - check for exact or wildcard match
2698                if self.struct_name.starts_with("*::") {
2699                    // Wildcard: *::Rectangle matches any path ending with Rectangle
2700                    let target_name = &self.struct_name[3..]; // Skip "*::"
2701                    expr_struct.path.segments.last()
2702                        .map(|seg| seg.ident.to_string() == target_name)
2703                        .unwrap_or(false)
2704                } else {
2705                    // Exact path match: View::Rectangle
2706                    let path_str = expr_struct.path.segments.iter()
2707                        .map(|seg| seg.ident.to_string())
2708                        .collect::<Vec<_>>()
2709                        .join("::");
2710                    path_str == self.struct_name
2711                }
2712            } else {
2713                // No :: in pattern - only match pure struct literals (no path qualifier)
2714                expr_struct.path.segments.len() == 1
2715                    && expr_struct.path.segments.last()
2716                        .map(|seg| seg.ident.to_string())
2717                        .as_ref() == Some(&self.struct_name)
2718            };
2719
2720            if is_match {
2721                // Check if field already exists (idempotent)
2722                let field_exists = expr_struct.fields.iter().any(|fv| {
2723                    fv.member.to_token_stream().to_string() == self.field_name
2724                });
2725
2726                if !field_exists {
2727                    // Parse the field value from field_def
2728                    // field_def is like "return_type: None"
2729                    let field_value_code = format!("{{ {} }}", self.field_def);
2730                    if let Ok(expr) = parse_str::<ExprStruct>(&format!("Dummy {}", field_value_code)) {
2731                        if let Some(new_fv) = expr.fields.first() {
2732                            // Determine where to insert
2733                            match &self.position {
2734                                InsertPosition::First => {
2735                                    expr_struct.fields.insert(0, new_fv.clone());
2736                                    self.modified = true;
2737                                }
2738                                InsertPosition::Last => {
2739                                    expr_struct.fields.push(new_fv.clone());
2740                                    self.modified = true;
2741                                }
2742                                InsertPosition::After(after_field) => {
2743                                    // Find the position of the field to insert after
2744                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2745                                        fv.member.to_token_stream().to_string() == *after_field
2746                                    }) {
2747                                        expr_struct.fields.insert(pos + 1, new_fv.clone());
2748                                        self.modified = true;
2749                                    }
2750                                }
2751                                InsertPosition::Before(before_field) => {
2752                                    // Find the position of the field to insert before
2753                                    if let Some(pos) = expr_struct.fields.iter().position(|fv| {
2754                                        fv.member.to_token_stream().to_string() == *before_field
2755                                    }) {
2756                                        expr_struct.fields.insert(pos, new_fv.clone());
2757                                        self.modified = true;
2758                                    }
2759                                }
2760                            }
2761                        }
2762                    }
2763                }
2764            }
2765        }
2766
2767        // IMPORTANT: Visit children AFTER processing this node
2768        // This ensures we traverse into nested expressions
2769        syn::visit_mut::visit_expr_mut(self, node);
2770    }
2771}