termwright_protocol/
diffing.rs1use std::collections::{HashMap, HashSet};
17
18use serde_json::{Map, Value};
19
20pub const DELTA_SHARE_CEILING: f64 = 0.5;
24
25fn nodes_of(tree: &Value) -> &[Value] {
26 tree.get("nodes")
27 .and_then(Value::as_array)
28 .map_or(&[], Vec::as_slice)
29}
30
31fn id_of(node: &Value) -> &str {
32 node.get("id").and_then(Value::as_str).unwrap_or_default()
33}
34
35pub fn build_delta(base: &Value, next: &Value) -> Option<Value> {
42 let (changed, removed, root_ids, cursor_changed) = diff_trees(base, next);
43
44 let count = nodes_of(next).len().max(1);
45 if changed.len() as f64 > count as f64 * DELTA_SHARE_CEILING {
46 return None;
47 }
48 if base.get("cursor").is_some() && next.get("cursor").is_none() {
49 return None;
54 }
55
56 let mut delta = Map::new();
57 delta.insert("type".into(), Value::from("tree-delta"));
58 delta.insert("baseRevision".into(), base.get("revision").cloned()?);
59 delta.insert("revision".into(), next.get("revision").cloned()?);
60 delta.insert("changed".into(), Value::Array(changed));
61 delta.insert("removed".into(), Value::Array(removed));
62 if let Some(root_ids) = root_ids {
63 delta.insert("rootIds".into(), Value::Array(root_ids));
64 }
65 if cursor_changed {
67 if let Some(cursor) = next.get("cursor") {
68 delta.insert("cursor".into(), cursor.clone());
69 }
70 }
71 Some(Value::Object(delta))
72}
73
74pub fn diff_trees(
77 base: &Value,
78 next: &Value,
79) -> (Vec<Value>, Vec<Value>, Option<Vec<Value>>, bool) {
80 let base_nodes = nodes_of(base);
81 let next_nodes = nodes_of(next);
82
83 let mut base_by_id: HashMap<&str, &Value> = HashMap::with_capacity(base_nodes.len());
84 let mut children_of: HashMap<&str, Vec<&str>> = HashMap::new();
85 for node in base_nodes {
86 base_by_id.insert(id_of(node), node);
87 if let Some(parent) = node.get("parentId").and_then(Value::as_str) {
88 children_of.entry(parent).or_default().push(id_of(node));
89 }
90 }
91 let next_by_id: HashMap<&str, &Value> =
92 next_nodes.iter().map(|node| (id_of(node), node)).collect();
93
94 let gone: HashSet<&str> = base_by_id
95 .keys()
96 .copied()
97 .filter(|id| !next_by_id.contains_key(id))
98 .collect();
99
100 let mut removal_roots: Vec<&str> = gone
103 .iter()
104 .copied()
105 .filter(
106 |id| match base_by_id[id].get("parentId").and_then(Value::as_str) {
107 Some(parent) => !gone.contains(parent),
108 None => true,
109 },
110 )
111 .collect();
112 removal_roots.sort_unstable();
113
114 let mut swept: HashSet<&str> = HashSet::new();
116 let mut pending: Vec<&str> = removal_roots.clone();
117 while let Some(current) = pending.pop() {
118 if !swept.insert(current) {
119 continue;
120 }
121 if let Some(children) = children_of.get(current) {
122 pending.extend(children.iter().copied());
123 }
124 }
125
126 let changed: Vec<Value> = next_nodes
128 .iter()
129 .filter(|node| {
130 let id = id_of(node);
131 match base_by_id.get(id) {
132 None => true,
133 Some(previous) => swept.contains(id) || *previous != *node,
134 }
135 })
136 .cloned()
137 .collect();
138
139 let removed: Vec<Value> = removal_roots.iter().map(|id| Value::from(*id)).collect();
140
141 let survivors: HashSet<&str> = base_by_id
142 .keys()
143 .copied()
144 .filter(|id| !swept.contains(id))
145 .chain(next_by_id.keys().copied())
146 .collect();
147 let inherited: Vec<&str> =
148 base.get("rootIds")
149 .and_then(Value::as_array)
150 .map_or(Vec::new(), |roots| {
151 roots
152 .iter()
153 .filter_map(Value::as_str)
154 .filter(|id| survivors.contains(id))
155 .collect()
156 });
157 let wanted: Vec<&str> = next
158 .get("rootIds")
159 .and_then(Value::as_array)
160 .map_or(Vec::new(), |roots| {
161 roots.iter().filter_map(Value::as_str).collect()
162 });
163 let root_ids = if inherited == wanted {
164 None
165 } else {
166 Some(wanted.iter().map(|id| Value::from(*id)).collect())
167 };
168
169 let cursor_changed = base.get("cursor") != next.get("cursor");
170 (changed, removed, root_ids, cursor_changed)
171}