Repository: Textualize/rich

Issue:
[REQUEST] Allow delimiters when using `pprint(..., max_depth=...)`
**How would you improve Rich?**

Currently, when pretty printing a nested object, we can set the max depth to simplify the object display:
```python
>>> from rich.pretty import pprint
>>> pprint({"a": {"b": {"c": "d"}}, "e": ["f", "g"]}, max_depth=1)
{'a': ..., 'e': ...}
```

However, by using ellipsis like this, we obscure the information about the data type of "a". In contrast, Python's built-in `pprint` module adds delimiters around the ellipsis, to differentiate between a list and a dict:
```python
>>> import pprint
>>> pprint.pprint({"a": {"b": {"c": "d"}}, "e": ["f", "g"]}, depth=1)
{'a': {...}, 'e': [...]}
```

Notice how, with only 4 extra characters, we have significantly more information about the eluded structure.

I would like to propose that we add a new argument to `pprint()` such as `max_depth_delims: bool`, which when True, would enable the behaviour above.

**What problem does it solve for you?**

When I use `max_depth` in tutorial or example, it's because I don't want unnecessary detail taking up a large amount of screen real estate. However I still want my users to understand that there *is* additional detail available, and to give them a hint as to what kind of data is nested further in the tree, and how to access it. Adding these delimiters nicely enable this compromise.


Discussion:
That seems like a good idea. I probably wouldn't bother with the boolean and just make it the default.
That would certainly make it easier. I'm happy to implement this, by the way.
PR would be welcome!

Resolving patch:
diff --git a/rich/pretty.py b/rich/pretty.py
--- a/rich/pretty.py
+++ b/rich/pretty.py
@@ -676,7 +676,10 @@ def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
                 append = children.append
 
                 if reached_max_depth:
-                    node = Node(value_repr=f"...")
+                    if angular:
+                        node = Node(value_repr=f"<{class_name}...>")
+                    else:
+                        node = Node(value_repr=f"{class_name}(...)")
                 else:
                     if angular:
                         node = Node(
@@ -718,7 +721,7 @@ def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]:
             attr_fields = _get_attr_fields(obj)
             if attr_fields:
                 if reached_max_depth:
-                    node = Node(value_repr=f"...")
+                    node = Node(value_repr=f"{obj.__class__.__name__}(...)")
                 else:
                     node = Node(
                         open_brace=f"{obj.__class__.__name__}(",
@@ -774,7 +777,7 @@ def iter_attrs() -> Iterable[
             children = []
             append = children.append
             if reached_max_depth:
-                node = Node(value_repr=f"...")
+                node = Node(value_repr=f"{obj.__class__.__name__}(...)")
             else:
                 node = Node(
                     open_brace=f"{obj.__class__.__name__}(",
@@ -794,18 +797,21 @@ def iter_attrs() -> Iterable[
 
                 pop_visited(obj_id)
         elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj):
+            class_name = obj.__class__.__name__
             if reached_max_depth:
-                node = Node(value_repr="...")
+                # If we've reached the max depth, we still show the class name, but not its contents
+                node = Node(
+                    value_repr=f"{class_name}(...)",
+                )
             else:
                 children = []
-                class_name = obj.__class__.__name__
+                append = children.append
                 node = Node(
                     open_brace=f"{class_name}(",
                     close_brace=")",
                     children=children,
                     empty=f"{class_name}()",
                 )
-                append = children.append
                 for last, (key, value) in loop_last(obj._asdict().items()):
                     child_node = _traverse(value, depth=depth + 1)
                     child_node.key_repr = key
@@ -827,7 +833,7 @@ def iter_attrs() -> Iterable[
             open_brace, close_brace, empty = _BRACES[obj_type](obj)
 
             if reached_max_depth:
-                node = Node(value_repr=f"...", last=root)
+                node = Node(value_repr=f"{open_brace}...{close_brace}")
             elif obj_type.__repr__ != type(obj).__repr__:
                 node = Node(value_repr=to_repr(obj), last=root)
             elif obj:


Test patch:
diff --git a/tests/test_pretty.py b/tests/test_pretty.py
--- a/tests/test_pretty.py
+++ b/tests/test_pretty.py
@@ -256,7 +256,7 @@ class LooksLikeANamedTupleButIsnt(tuple):
 def test_pretty_namedtuple_max_depth():
     instance = {"unit": StockKeepingUnit("a", "b", 1.0, "c", ["d", "e"])}
     result = pretty_repr(instance, max_depth=1)
-    assert result == "{'unit': ...}"
+    assert result == "{'unit': StockKeepingUnit(...)}"
 
 
 def test_small_width():
@@ -318,13 +318,13 @@ def test_max_depth():
     d = {}
     d["foo"] = {"fob": {"a": [1, 2, 3], "b": {"z": "x", "y": ["a", "b", "c"]}}}
 
-    assert pretty_repr(d, max_depth=0) == "..."
-    assert pretty_repr(d, max_depth=1) == "{'foo': ...}"
-    assert pretty_repr(d, max_depth=2) == "{'foo': {'fob': ...}}"
-    assert pretty_repr(d, max_depth=3) == "{'foo': {'fob': {'a': ..., 'b': ...}}}"
+    assert pretty_repr(d, max_depth=0) == "{...}"
+    assert pretty_repr(d, max_depth=1) == "{'foo': {...}}"
+    assert pretty_repr(d, max_depth=2) == "{'foo': {'fob': {...}}}"
+    assert pretty_repr(d, max_depth=3) == "{'foo': {'fob': {'a': [...], 'b': {...}}}}"
     assert (
         pretty_repr(d, max_width=100, max_depth=4)
-        == "{'foo': {'fob': {'a': [1, 2, 3], 'b': {'z': 'x', 'y': ...}}}}"
+        == "{'foo': {'fob': {'a': [1, 2, 3], 'b': {'z': 'x', 'y': [...]}}}}"
     )
     assert (
         pretty_repr(d, max_width=100, max_depth=5)
@@ -353,7 +353,7 @@ def __rich_repr__(self):
 
     assert (
         pretty_repr(Foo(foo=Bar(bar=Foo(foo=[]))), max_depth=2)
-        == "Foo(foo=Bar(bar=...))"
+        == "Foo(foo=Bar(bar=Foo(...)))"
     )
 
 
@@ -368,7 +368,7 @@ class Bar:
 
     assert (
         pretty_repr(Foo(foo=Bar(bar=Foo(foo=[]))), max_depth=2)
-        == "Foo(foo=Bar(bar=...))"
+        == "Foo(foo=Bar(bar=Foo(...)))"
     )
 
 
@@ -383,7 +383,7 @@ class Bar:
 
     assert (
         pretty_repr(Foo(foo=Bar(bar=Foo(foo=[]))), max_depth=2)
-        == "Foo(foo=Bar(bar=...))"
+        == "Foo(foo=Bar(bar=Foo(...)))"
     )