WIP

Antonio Scandurra , Nathan Sobo , and Max Brunsfeld created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
Co-Authored-By: Max Brunsfeld <max@zed.dev>

Change summary

zed/src/theme.rs                |   1 
zed/src/theme/resolve_tree.rs   | 275 +++++++++++++++++++++++++++++++++++
zed/src/theme/theme_registry.rs |  34 ++--
3 files changed, 293 insertions(+), 17 deletions(-)

Detailed changes

zed/src/theme.rs 🔗

@@ -1,4 +1,5 @@
 mod highlight_map;
+mod resolve_tree;
 mod theme_registry;
 
 use anyhow::Result;

zed/src/theme/resolve_tree.rs 🔗

@@ -0,0 +1,275 @@
+use anyhow::{anyhow, Result};
+use serde_json::Value;
+use std::{
+    cell::RefCell,
+    collections::HashMap,
+    mem,
+    rc::{Rc, Weak},
+};
+
+pub fn resolve(value: Value) -> Result<Value> {
+    let tree = Tree::from_json(value)?;
+    tree.resolve()?;
+    tree.to_json()
+}
+
+#[derive(Clone)]
+enum Node {
+    Reference {
+        path: String,
+        parent: Option<Weak<RefCell<Node>>>,
+    },
+    Object {
+        base: Option<String>,
+        children: HashMap<String, Tree>,
+        resolved: bool,
+        parent: Option<Weak<RefCell<Node>>>,
+    },
+    String {
+        value: String,
+        parent: Option<Weak<RefCell<Node>>>,
+    },
+}
+
+#[derive(Clone)]
+struct Tree(Rc<RefCell<Node>>);
+
+impl Tree {
+    pub fn new(node: Node) -> Self {
+        Self(Rc::new(RefCell::new(node)))
+    }
+
+    fn from_json(value: Value) -> Result<Self> {
+        match value {
+            Value::String(s) => {
+                if let Some(path) = s.strip_prefix("$") {
+                    Ok(Self::new(Node::Reference {
+                        path: path.to_string(),
+                        parent: None,
+                    }))
+                } else {
+                    Ok(Self::new(Node::String {
+                        value: s,
+                        parent: None,
+                    }))
+                }
+            }
+            Value::Object(object) => {
+                let mut tree = Self::new(Node::Object {
+                    base: Default::default(),
+                    children: Default::default(),
+                    resolved: false,
+                    parent: None,
+                });
+                let mut children = HashMap::new();
+                let mut resolved = true;
+                let mut base = None;
+                for (key, value) in object.into_iter() {
+                    if key == "extends" {
+                        if let Value::String(s) = value {
+                            base = Some(s);
+                            resolved = false;
+                        }
+                    } else {
+                        let value = Tree::from_json(value)?;
+                        value
+                            .0
+                            .borrow_mut()
+                            .set_parent(Some(Rc::downgrade(&tree.0)));
+                        resolved &= value.is_resolved();
+                        children.insert(key.clone(), value);
+                    }
+                }
+
+                *tree.0.borrow_mut() = Node::Object {
+                    base,
+                    children,
+                    resolved,
+                    parent: None,
+                };
+                Ok(tree)
+            }
+            _ => return Err(anyhow!("unsupported json type")),
+        }
+    }
+
+    fn to_json(&self) -> Result<Value> {
+        match &*self.0.borrow() {
+            Node::Reference { .. } => Err(anyhow!("unresolved tree")),
+            Node::String { value, .. } => Ok(Value::String(value.clone())),
+            Node::Object { children, .. } => {
+                let mut json_children = serde_json::Map::new();
+                for (key, value) in children {
+                    json_children.insert(key.clone(), value.to_json()?);
+                }
+                Ok(Value::Object(json_children))
+            }
+            _ => unimplemented!(),
+        }
+    }
+
+    fn parent(&self) -> Option<Tree> {
+        match &*self.0.borrow() {
+            Node::Reference { parent, .. }
+            | Node::Object { parent, .. }
+            | Node::String { parent, .. } => parent.as_ref().and_then(|p| p.upgrade()).map(Tree),
+        }
+    }
+
+    fn get(&self, path: &str) -> Result<Option<Tree>> {
+        let mut tree = self.clone();
+        for component in path.split('.') {
+            let node = tree.0.borrow();
+            match &*node {
+                Node::Object { children, .. } => {
+                    if let Some(subtree) = children.get(component).cloned() {
+                        drop(node);
+                        tree = subtree;
+                    } else {
+                        return Err(anyhow!("key does not exist"));
+                    }
+                }
+                Node::Reference { .. } => return Ok(None),
+                Node::String { .. } => return Err(anyhow!("component is not an object")),
+            }
+        }
+
+        Ok(Some(tree))
+    }
+
+    fn is_resolved(&self) -> bool {
+        match &*self.0.borrow() {
+            Node::Reference { .. } => false,
+            Node::Object { resolved, .. } => *resolved,
+            Node::String { .. } => true,
+        }
+    }
+
+    fn update_resolved(&self) {
+        match &mut *self.0.borrow_mut() {
+            Node::Object {
+                resolved, children, ..
+            } => {
+                *resolved = children.values().all(|c| c.is_resolved());
+            }
+            _ => {}
+        }
+    }
+
+    pub fn resolve(&self) -> Result<()> {
+        let mut unresolved = vec![self.clone()];
+        let mut made_progress = true;
+        while made_progress && !unresolved.is_empty() {
+            made_progress = false;
+            dbg!("===========");
+            for mut tree in mem::take(&mut unresolved) {
+                made_progress |= tree.resolve_subtree(self, &mut unresolved)?;
+                if tree.is_resolved() {
+                    while let Some(parent) = tree.parent() {
+                        parent.update_resolved();
+                        tree = parent;
+                    }
+                }
+            }
+        }
+
+        if unresolved.is_empty() {
+            Ok(())
+        } else {
+            Err(anyhow!("could not resolve tree"))
+        }
+    }
+
+    fn resolve_subtree(&self, root: &Tree, unresolved: &mut Vec<Tree>) -> Result<bool> {
+        let mut made_progress = false;
+        let borrow = self.0.borrow();
+        match &*borrow {
+            Node::Reference { path, parent } => {
+                print!("entering reference ${}: ", path);
+                if let Some(subtree) = root.get(&path)? {
+                    if subtree.is_resolved() {
+                        println!("resolved");
+                        let parent = parent.clone();
+                        drop(borrow);
+                        let mut new_node = subtree.0.borrow().clone();
+                        new_node.set_parent(parent);
+                        *self.0.borrow_mut() = new_node;
+                        Ok(true)
+                    } else {
+                        println!("unresolved (but existing)");
+                        unresolved.push(self.clone());
+                        Ok(false)
+                    }
+                } else {
+                    println!("unresolved (referant does not exist)");
+                    unresolved.push(self.clone());
+                    Ok(false)
+                }
+            }
+            Node::Object {
+                base,
+                children,
+                resolved,
+                ..
+            } => {
+                if *resolved {
+                    println!("already resolved");
+                    Ok(false)
+                } else {
+                    let mut children_resolved = true;
+                    for (key, child) in children.iter() {
+                        println!("resolving subtree {}", key);
+                        made_progress |= child.resolve_subtree(root, unresolved)?;
+                        children_resolved &= child.is_resolved();
+                    }
+
+                    if children_resolved {
+                        drop(borrow);
+                        if let Node::Object { resolved, .. } = &mut *self.0.borrow_mut() {
+                            *resolved = true;
+                        }
+                    }
+
+                    Ok(made_progress)
+                }
+            }
+            Node::String { value, .. } => {
+                println!("terminating at string: {}", value);
+                return Ok(false);
+            }
+        }
+    }
+}
+
+impl Node {
+    fn set_parent(&mut self, new_parent: Option<Weak<RefCell<Node>>>) {
+        match self {
+            Node::Reference { parent, .. }
+            | Node::Object { parent, .. }
+            | Node::String { parent, .. } => *parent = new_parent,
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_basic() {
+        let json = serde_json::json!({
+            "a": {
+                "x": "$b.d"
+            },
+            "b": {
+                "c": "$a",
+                "d": "$e.f"
+            },
+            "e": {
+                "f": "1"
+            }
+        });
+
+        dbg!(resolve(json).unwrap());
+    }
+}

zed/src/theme/theme_registry.rs 🔗

@@ -336,7 +336,7 @@ impl KeyPathReferenceSet {
                 &self.references,
                 &self.reference_ids_by_target,
                 KeyPathReference::target,
-                PartialEq::eq,
+                KeyPath::starts_with,
             ) {
                 Self::add_dependency(
                     (new_id, id),
@@ -733,20 +733,20 @@ mod tests {
     #[gpui::test(iterations = 20)]
     async fn test_key_path_reference_set_random(mut rng: StdRng) {
         let examples: &[&[_]] = &[
-            &[
-                ("n.d.h", "i"),
-                ("f.g", "n.d.h"),
-                ("n.d.e", "f"),
-                ("a.b.c", "n.d"),
-                ("r", "a"),
-                ("q.q.q", "r.s"),
-                ("r.t", "q"),
-                ("x.x", "r.r"),
-                ("v.w", "x"),
-                ("v.y", "x"),
-                ("v.z", "x"),
-                ("t.u", "v"),
-            ],
+            // &[
+            //     ("n.d.h", "i"),
+            //     ("f.g", "n.d.h"),
+            //     ("n.d.e", "f"),
+            //     ("a.b.c", "n.d"),
+            //     ("r", "a"),
+            //     ("q.q.q", "r.s"),
+            //     ("r.t", "q"),
+            //     ("x.x", "r.r"),
+            //     ("v.w", "x"),
+            //     ("v.y", "x"),
+            //     ("v.z", "x"),
+            //     ("t.u", "v"),
+            // ],
             &[
                 ("w.x.y.z", "t.u.z"),
                 ("x", "w.x"),
@@ -754,13 +754,13 @@ mod tests {
                 ("a.b.c2", "x.b2.c"),
             ],
             &[
-                ("x.y", "m.n.n.o.q"),
                 ("x.y.z", "m.n.n.o.p"),
+                ("x.y", "m.n.n.o.q"),
                 ("u.v.w", "x.y.z"),
-                ("a.b.c.d", "u.v"),
                 ("a.b.c.d.e", "u.v"),
                 ("a.b.c.d.f", "u.v"),
                 ("a.b.c.d.g", "u.v"),
+                ("a.b.c.d", "u.v"),
             ],
         ];