whiteout/parser/
apply.rs

1use super::types::Decoration;
2
3/// Apply decorations to content
4pub fn apply_decorations(
5    content: &str,
6    decorations: &[Decoration],
7    use_local: bool,
8) -> String {
9    if decorations.is_empty() {
10        return content.to_string();
11    }
12
13    let lines: Vec<&str> = content.lines().collect();
14    let mut result = Vec::new();
15    let mut skip_until = 0;
16
17    for (idx, line) in lines.iter().enumerate() {
18        let line_num = idx + 1;
19        
20        if line_num <= skip_until {
21            continue;
22        }
23
24        let mut line_processed = false;
25        
26        // Check for block decorations
27        for decoration in decorations {
28            if let Decoration::Block { start_line, end_line, local_content, committed_content } = decoration {
29                if line_num == *start_line {
30                    if use_local {
31                        // Smudge: Check if this is a simple @whiteout or block with markers
32                        let is_simple_pattern = line.contains("@whiteout") && 
33                                              !line.contains("@whiteout-start") && 
34                                              !line.contains("@whiteout:");
35                        
36                        if is_simple_pattern {
37                            // Simple @whiteout: Keep marker and show local content
38                            result.push(line.to_string()); // Keep @whiteout marker
39                            for content_line in local_content.lines() {
40                                result.push(content_line.to_string());
41                            }
42                            // Skip to end of the block
43                            skip_until = *end_line;
44                        } else {
45                            // Block with markers: Keep markers and show local content
46                            result.push(line.to_string()); // Keep @whiteout-start
47                            for content_line in local_content.lines() {
48                                result.push(content_line.to_string());
49                            }
50                            // Find and add the end marker
51                            if *end_line <= lines.len() {
52                                result.push(lines[*end_line - 1].to_string()); // Keep @whiteout-end
53                            }
54                            // Skip the committed content that follows
55                            skip_until = *end_line;
56                            
57                            // Count lines of committed content to skip
58                            let committed_lines = committed_content.lines().count();
59                            if committed_lines > 0 {
60                                skip_until += committed_lines;
61                            }
62                        }
63                    } else {
64                        // Clean: Check if this is a simple @whiteout or block with markers
65                        let is_simple_pattern = line.contains("@whiteout") && 
66                                              !line.contains("@whiteout-start") && 
67                                              !line.contains("@whiteout:");
68                        
69                        if is_simple_pattern {
70                            // Simple @whiteout: Remove the marker and skip the local content
71                            // Don't push the @whiteout marker line
72                            // Skip all the local content lines
73                            skip_until = *end_line;
74                        } else {
75                            // Block with @whiteout-start/end: Keep markers with empty content
76                            result.push(line.to_string()); // Keep @whiteout-start
77                            // No local content in between (it's been cleaned)
78                            
79                            // Add the end marker
80                            if *end_line <= lines.len() {
81                                result.push(lines[*end_line - 1].to_string()); // Keep @whiteout-end
82                            }
83                            
84                            // Add the committed content that follows the block
85                            if !committed_content.is_empty() {
86                                for content_line in committed_content.lines() {
87                                    result.push(content_line.to_string());
88                                }
89                            }
90                            
91                            // Skip to end of original block plus any following committed content
92                            skip_until = *end_line + committed_content.lines().count();
93                        }
94                    }
95                    line_processed = true;
96                    break;
97                }
98            }
99        }
100        
101        if line_processed {
102            continue;
103        }
104        
105        // Check for inline decorations
106        let mut found_inline = false;
107        for decoration in decorations {
108            if let Decoration::Inline { line: dec_line, local_value, committed_value } = decoration {
109                if line_num == *dec_line {
110                    if use_local {
111                        // Smudge: Show local value with decoration
112                        result.push(format!("{} // @whiteout: {}", local_value, committed_value));
113                    } else {
114                        // Clean: Show committed value WITH decoration marker for smudge to work
115                        result.push(format!("{} // @whiteout: {}", committed_value, committed_value));
116                    }
117                    found_inline = true;
118                    line_processed = true;
119                    break;
120                }
121            }
122        }
123        
124        if found_inline {
125            continue;
126        }
127        
128        // Check for partial replacements
129        for decoration in decorations {
130            if let Decoration::Partial { line: dec_line, replacements } = decoration {
131                if line_num == *dec_line {
132                    let mut processed_line = line.to_string();
133                    
134                    for replacement in replacements.iter().rev() {
135                        let new_value = if use_local {
136                            // Smudge: Use local value in the pattern
137                            format!("[[{}||{}]]", 
138                                replacement.local_value, 
139                                replacement.committed_value)
140                        } else {
141                            // Clean: Preserve pattern structure with committed value for smudge to work
142                            format!("[[{}||{}]]", 
143                                replacement.committed_value.clone(),
144                                replacement.committed_value)
145                        };
146                        
147                        if replacement.start < processed_line.len() {
148                            processed_line.replace_range(
149                                replacement.start..replacement.end.min(processed_line.len()),
150                                &new_value
151                            );
152                        }
153                    }
154                    
155                    result.push(processed_line);
156                    line_processed = true;
157                    break;
158                }
159            }
160        }
161        
162        if !line_processed {
163            result.push(line.to_string());
164        }
165    }
166    
167    let mut output = result.join("\n");
168    // Preserve trailing newline if original had one
169    if content.ends_with('\n') && !output.ends_with('\n') {
170        output.push('\n');
171    }
172    output
173}