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 if let Some(insertions) = theirs_insertions.get(&Some(entity_region.entity_id.clone())) {
94 for theirs_entity in insertions {
95 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
96 match resolved {
97 ResolvedEntity::Clean(region) => {
98 output.push('\n');
99 output.push_str(®ion.content);
100 if !region.content.is_empty()
101 && !region.content.ends_with('\n')
102 {
103 output.push('\n');
104 }
105 }
106 ResolvedEntity::Conflict(conflict) => {
107 output.push('\n');
108 output.push_str(&conflict.to_conflict_markers(marker_format));
109 }
110 ResolvedEntity::ScopedConflict { content, .. } => {
111 output.push('\n');
112 output.push_str(content);
113 if !content.is_empty() && !content.ends_with('\n') {
114 output.push('\n');
115 }
116 }
117 ResolvedEntity::Deleted => {}
118 }
119 }
120 emitted_entities.insert(theirs_entity.id.clone());
121 }
122 }
123 }
124 }
125 }
126
127 if let Some(insertions) = theirs_insertions.get(&None) {
130 for theirs_entity in insertions {
131 if !emitted_entities.contains(&theirs_entity.id) {
132 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
133 emit_resolved(&mut output, resolved, marker_format);
134 }
135 emitted_entities.insert(theirs_entity.id.clone());
136 }
137 }
138 }
139
140 for (pred, insertions) in &theirs_insertions {
142 if pred.is_none() {
143 continue; }
145 for theirs_entity in insertions {
146 if !emitted_entities.contains(&theirs_entity.id) {
147 if let Some(resolved) = resolved_entities.get(&theirs_entity.id) {
148 emit_resolved(&mut output, resolved, marker_format);
149 }
150 emitted_entities.insert(theirs_entity.id.clone());
151 }
152 }
153 }
154
155 output
156}
157
158fn emit_resolved(output: &mut String, resolved: &ResolvedEntity, marker_format: &MarkerFormat) {
160 match resolved {
161 ResolvedEntity::Clean(region) => {
162 if !output.is_empty() && !output.ends_with('\n') {
163 output.push('\n');
164 }
165 output.push('\n');
166 output.push_str(®ion.content);
167 if !region.content.is_empty() && !region.content.ends_with('\n') {
168 output.push('\n');
169 }
170 }
171 ResolvedEntity::Conflict(conflict) => {
172 if !output.is_empty() && !output.ends_with('\n') {
173 output.push('\n');
174 }
175 output.push('\n');
176 output.push_str(&conflict.to_conflict_markers(marker_format));
177 }
178 ResolvedEntity::ScopedConflict { content, .. } => {
179 if !output.is_empty() && !output.ends_with('\n') {
180 output.push('\n');
181 }
182 output.push('\n');
183 output.push_str(content);
184 if !content.is_empty() && !content.ends_with('\n') {
185 output.push('\n');
186 }
187 }
188 ResolvedEntity::Deleted => {}
189 }
190}
191
192fn find_predecessor_in_regions(regions: &[FileRegion], entity_id: &str) -> Option<String> {
194 let mut last_entity_id: Option<String> = None;
195 for region in regions {
196 if let FileRegion::Entity(e) = region {
197 if e.entity_id == entity_id {
198 return last_entity_id;
199 }
200 last_entity_id = Some(e.entity_id.clone());
201 }
202 }
203 None
204}