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) -> String {
22 let mut output = String::new();
23
24 let mut emitted_entities: std::collections::HashSet<String> = std::collections::HashSet::new();
26
27 let theirs_only: Vec<&SemanticEntity> = theirs_entities
29 .iter()
30 .filter(|e| !ours_entity_map.contains_key(e.id.as_str()))
31 .collect();
32
33 let mut theirs_insertions: HashMap<Option<String>, Vec<&SemanticEntity>> = HashMap::new();
35 for entity in &theirs_only {
36 let predecessor = find_predecessor_in_regions(theirs_regions, &entity.id);
37 theirs_insertions
38 .entry(predecessor)
39 .or_default()
40 .push(entity);
41 }
42
43 for region in ours_regions {
45 match region {
46 FileRegion::Interstitial(interstitial) => {
47 if let Some(merged) = merged_interstitials.get(&interstitial.position_key) {
49 output.push_str(merged);
50 } else {
51 output.push_str(&interstitial.content);
52 }
53 }
54 FileRegion::Entity(entity_region) => {
55 if let Some(resolved) = resolved_entities.get(&entity_region.entity_id) {
60 match resolved {
61 ResolvedEntity::Clean(region) => {
62 output.push_str(®ion.content);
63 if !region.content.is_empty() && !region.content.ends_with('\n') {
64 output.push('\n');
65 }
66 }
67 ResolvedEntity::Conflict(conflict) => {
68 output.push_str(&conflict.to_conflict_markers(marker_format));
69 }
70 ResolvedEntity::ScopedConflict { content, .. } => {
71 output.push_str(content);
72 if !content.is_empty() && !content.ends_with('\n') {
73 output.push('\n');
74 }
75 }
76 ResolvedEntity::Deleted => {
77 }
79 }
80 } else {
81 output.push_str(&entity_region.content);
83 if !entity_region.content.is_empty()
84 && !entity_region.content.ends_with('\n')
85 {
86 output.push('\n');
87 }
88 }
89
90 emitted_entities.insert(entity_region.entity_id.clone());
91
92 let mut current_pred = Some(entity_region.entity_id.clone());
98 while let Some(ref pred) = current_pred {
99 if let Some(insertions) = theirs_insertions.get(&Some(pred.clone())) {
100 let mut next_pred: Option<String> = None;
101 for theirs_entity in insertions {
102 if emitted_entities.contains(&theirs_entity.id) {
103 continue;
104 }
105 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
106 match resolved {
107 ResolvedEntity::Clean(region) => {
108 if region.content.trim_end().contains('\n') {
112 output.push('\n');
113 }
114 output.push_str(®ion.content);
115 if !region.content.is_empty()
116 && !region.content.ends_with('\n')
117 {
118 output.push('\n');
119 }
120 }
121 ResolvedEntity::Conflict(conflict) => {
122 output.push('\n');
123 output.push_str(&conflict.to_conflict_markers(marker_format));
124 }
125 ResolvedEntity::ScopedConflict { content, .. } => {
126 output.push('\n');
127 output.push_str(content);
128 if !content.is_empty() && !content.ends_with('\n') {
129 output.push('\n');
130 }
131 }
132 ResolvedEntity::Deleted => {}
133 }
134 }
135 emitted_entities.insert(theirs_entity.id.clone());
136 next_pred = Some(theirs_entity.id.clone());
137 }
138 current_pred = next_pred;
139 } else {
140 break;
141 }
142 }
143 }
144 }
145 }
146
147 if let Some(insertions) = theirs_insertions.get(&None) {
150 for theirs_entity in insertions {
151 if !emitted_entities.contains(&theirs_entity.id) {
152 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
153 emit_resolved(&mut output, resolved, marker_format);
154 }
155 emitted_entities.insert(theirs_entity.id.clone());
156 }
157 }
158 }
159
160 for (pred, insertions) in &theirs_insertions {
162 if pred.is_none() {
163 continue; }
165 for theirs_entity in insertions {
166 if !emitted_entities.contains(&theirs_entity.id) {
167 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
168 emit_resolved(&mut output, resolved, marker_format);
169 }
170 emitted_entities.insert(theirs_entity.id.clone());
171 }
172 }
173 }
174
175 output
176}
177
178fn emit_resolved(output: &mut String, resolved: &ResolvedEntity, marker_format: &MarkerFormat) {
180 match resolved {
181 ResolvedEntity::Clean(region) => {
182 if !output.is_empty() && !output.ends_with('\n') {
183 output.push('\n');
184 }
185 output.push('\n');
186 output.push_str(®ion.content);
187 if !region.content.is_empty() && !region.content.ends_with('\n') {
188 output.push('\n');
189 }
190 }
191 ResolvedEntity::Conflict(conflict) => {
192 if !output.is_empty() && !output.ends_with('\n') {
193 output.push('\n');
194 }
195 output.push('\n');
196 output.push_str(&conflict.to_conflict_markers(marker_format));
197 }
198 ResolvedEntity::ScopedConflict { content, .. } => {
199 if !output.is_empty() && !output.ends_with('\n') {
200 output.push('\n');
201 }
202 output.push('\n');
203 output.push_str(content);
204 if !content.is_empty() && !content.ends_with('\n') {
205 output.push('\n');
206 }
207 }
208 ResolvedEntity::Deleted => {}
209 }
210}
211
212fn find_predecessor_in_regions(regions: &[FileRegion], entity_id: &str) -> Option<String> {
214 let mut last_entity_id: Option<String> = None;
215 for region in regions {
216 if let FileRegion::Entity(e) = region {
217 if e.entity_id == entity_id {
218 return last_entity_id;
219 }
220 last_entity_id = Some(e.entity_id.clone());
221 }
222 }
223 None
224}