weave_core/
reconstruct.rs1use std::collections::HashMap;
2
3use sem_core::model::entity::SemanticEntity;
4
5use crate::conflict::MarkerFormat;
6use crate::merge::ResolvedEntity;
7use crate::region::FileRegion;
8
9pub fn reconstruct(
14 ours_regions: &[FileRegion],
15 theirs_regions: &[FileRegion],
16 theirs_entities: &[SemanticEntity],
17 ours_entity_map: &HashMap<&str, &SemanticEntity>,
18 resolved_entities: &HashMap<String, ResolvedEntity>,
19 merged_interstitials: &HashMap<String, String>,
20 marker_format: &MarkerFormat,
21 theirs_rename_base_ids: &std::collections::HashSet<String>,
22) -> String {
23 let mut output = String::new();
24
25 let mut emitted_entities: std::collections::HashSet<String> = std::collections::HashSet::new();
27
28 let theirs_only: Vec<&SemanticEntity> = theirs_entities
30 .iter()
31 .filter(|e| {
32 !ours_entity_map.contains_key(e.id.as_str())
33 && !theirs_rename_base_ids.contains(&e.id)
34 })
35 .collect();
36
37 let mut theirs_insertions: HashMap<Option<String>, Vec<&SemanticEntity>> = HashMap::new();
39 for entity in &theirs_only {
40 let predecessor = find_predecessor_in_regions(theirs_regions, &entity.id);
41 theirs_insertions
42 .entry(predecessor)
43 .or_default()
44 .push(entity);
45 }
46
47 for region in ours_regions {
49 match region {
50 FileRegion::Interstitial(interstitial) => {
51 if let Some(merged) = merged_interstitials.get(&interstitial.position_key) {
53 output.push_str(merged);
54 } else {
55 output.push_str(&interstitial.content);
56 }
57 }
58 FileRegion::Entity(entity_region) => {
59 if let Some(resolved) = resolved_entities.get(&entity_region.entity_id) {
64 match resolved {
65 ResolvedEntity::Clean(region) => {
66 output.push_str(®ion.content);
67 if !region.content.is_empty() && !region.content.ends_with('\n') {
68 output.push('\n');
69 }
70 }
71 ResolvedEntity::Conflict(conflict) => {
72 output.push_str(&conflict.to_conflict_markers(marker_format));
73 }
74 ResolvedEntity::ScopedConflict { content, .. } => {
75 output.push_str(content);
76 if !content.is_empty() && !content.ends_with('\n') {
77 output.push('\n');
78 }
79 }
80 ResolvedEntity::Deleted => {
81 }
83 }
84 } else {
85 output.push_str(&entity_region.content);
87 if !entity_region.content.is_empty()
88 && !entity_region.content.ends_with('\n')
89 {
90 output.push('\n');
91 }
92 }
93
94 emitted_entities.insert(entity_region.entity_id.clone());
95
96 let mut current_pred = Some(entity_region.entity_id.clone());
102 while let Some(ref pred) = current_pred {
103 if let Some(insertions) = theirs_insertions.get(&Some(pred.clone())) {
104 let mut next_pred: Option<String> = None;
105 for theirs_entity in insertions {
106 if emitted_entities.contains(&theirs_entity.id) {
107 continue;
108 }
109 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
110 match resolved {
111 ResolvedEntity::Clean(region) => {
112 if region.content.trim_end().contains('\n') {
116 output.push('\n');
117 }
118 output.push_str(®ion.content);
119 if !region.content.is_empty()
120 && !region.content.ends_with('\n')
121 {
122 output.push('\n');
123 }
124 }
125 ResolvedEntity::Conflict(conflict) => {
126 output.push('\n');
127 output.push_str(&conflict.to_conflict_markers(marker_format));
128 }
129 ResolvedEntity::ScopedConflict { content, .. } => {
130 output.push('\n');
131 output.push_str(content);
132 if !content.is_empty() && !content.ends_with('\n') {
133 output.push('\n');
134 }
135 }
136 ResolvedEntity::Deleted => {}
137 }
138 }
139 emitted_entities.insert(theirs_entity.id.clone());
140 next_pred = Some(theirs_entity.id.clone());
141 }
142 current_pred = next_pred;
143 } else {
144 break;
145 }
146 }
147 }
148 }
149 }
150
151 if let Some(insertions) = theirs_insertions.get(&None) {
154 for theirs_entity in insertions {
155 if !emitted_entities.contains(&theirs_entity.id) {
156 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
157 emit_resolved(&mut output, resolved, marker_format);
158 }
159 emitted_entities.insert(theirs_entity.id.clone());
160 }
161 }
162 }
163
164 for (pred, insertions) in &theirs_insertions {
166 if pred.is_none() {
167 continue; }
169 for theirs_entity in insertions {
170 if !emitted_entities.contains(&theirs_entity.id) {
171 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
172 emit_resolved(&mut output, resolved, marker_format);
173 }
174 emitted_entities.insert(theirs_entity.id.clone());
175 }
176 }
177 }
178
179 output
180}
181
182fn emit_resolved(output: &mut String, resolved: &ResolvedEntity, marker_format: &MarkerFormat) {
184 match resolved {
185 ResolvedEntity::Clean(region) => {
186 if !output.is_empty() && !output.ends_with('\n') {
187 output.push('\n');
188 }
189 output.push('\n');
190 output.push_str(®ion.content);
191 if !region.content.is_empty() && !region.content.ends_with('\n') {
192 output.push('\n');
193 }
194 }
195 ResolvedEntity::Conflict(conflict) => {
196 if !output.is_empty() && !output.ends_with('\n') {
197 output.push('\n');
198 }
199 output.push('\n');
200 output.push_str(&conflict.to_conflict_markers(marker_format));
201 }
202 ResolvedEntity::ScopedConflict { content, .. } => {
203 if !output.is_empty() && !output.ends_with('\n') {
204 output.push('\n');
205 }
206 output.push('\n');
207 output.push_str(content);
208 if !content.is_empty() && !content.ends_with('\n') {
209 output.push('\n');
210 }
211 }
212 ResolvedEntity::Deleted => {}
213 }
214}
215
216fn find_predecessor_in_regions(regions: &[FileRegion], entity_id: &str) -> Option<String> {
218 let mut last_entity_id: Option<String> = None;
219 for region in regions {
220 if let FileRegion::Entity(e) = region {
221 if e.entity_id == entity_id {
222 return last_entity_id;
223 }
224 last_entity_id = Some(e.entity_id.clone());
225 }
226 }
227 None
228}