Skip to main content

rich/
scope.rs

1//! Port of upstream rich/scope.py.
2//!
3//! [`render_scope`] draws a mapping of variable names to values (upstream
4//! passes `locals()`) as a two-column grid of `name =` and the value's
5//! [`Pretty`] repr, in a fitted panel. Rust has no reflection over a scope,
6//! so the caller passes the names and [`PrettyValue`]s.
7
8use std::sync::Arc;
9
10use crate::console::{Justify, Overflow};
11use crate::highlighter::ReprHighlighter;
12use crate::panel::Panel;
13use crate::pretty::{Pretty, PrettyValue};
14use crate::table::{Cell, Table};
15use crate::text::Text;
16
17/// The keyword arguments of `render_scope`.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct ScopeOptions {
20    /// A title for the panel, as console markup (upstream `title`).
21    pub title: Option<String>,
22    /// Sort the names: `__special__` names first, then case-insensitively
23    /// (upstream `sort_keys`, default `True`).
24    pub sort_keys: bool,
25    /// Draw indent guides in the values (upstream `indent_guides`).
26    pub indent_guides: bool,
27    /// Abbreviate containers longer than this (upstream `max_length`).
28    pub max_length: Option<usize>,
29    /// Truncate strings longer than this (upstream `max_string`).
30    pub max_string: Option<usize>,
31    /// Stop at this depth of nesting (upstream `max_depth`).
32    pub max_depth: Option<usize>,
33    /// How values that do not fit overflow (upstream `overflow`).
34    pub overflow: Option<Overflow>,
35}
36
37impl Default for ScopeOptions {
38    fn default() -> Self {
39        ScopeOptions {
40            title: None,
41            sort_keys: true,
42            indent_guides: false,
43            max_length: None,
44            max_string: None,
45            max_depth: None,
46            overflow: None,
47        }
48    }
49}
50
51/// Render variables in a scope: each name, `=`, and its pretty repr, in a
52/// panel. Port of `rich.scope.render_scope`.
53pub fn render_scope<K: Into<String>>(
54    scope: impl IntoIterator<Item = (K, PrettyValue)>,
55    options: &ScopeOptions,
56) -> Panel {
57    let mut items_table = Table::grid().padding(0, 1, 0, 1).expand(false);
58    items_table.add_column_justify("", Justify::Right);
59
60    let mut items: Vec<(String, PrettyValue)> = scope
61        .into_iter()
62        .map(|(key, value)| (key.into(), value))
63        .collect();
64    if options.sort_keys {
65        // Special variables first, then alphabetically. The sort is stable,
66        // as Python's `sorted` is.
67        items.sort_by_cached_key(|(key, _)| (!key.starts_with("__"), key.to_lowercase()));
68    }
69    for (key, value) in items {
70        let key_style = if key.starts_with("__") {
71            "scope.key.special"
72        } else {
73            "scope.key"
74        };
75        let mut key_text = Text::new("");
76        key_text.append(&key, Some(key_style.into()));
77        key_text.append(" =", Some("scope.equals".into()));
78        let pretty = Pretty::from_value(value)
79            .highlighter(ReprHighlighter::new())
80            .indent_guides(options.indent_guides)
81            .max_length(options.max_length)
82            .max_string(options.max_string)
83            .max_depth(options.max_depth)
84            .overflow(options.overflow);
85        items_table.add_row_cells(vec![
86            Cell::Text(key_text),
87            Cell::Renderable(Arc::new(pretty)),
88        ]);
89    }
90    let mut panel = Panel::fit(Box::new(items_table))
91        .border_style("scope.border")
92        .padding((0, 1, 0, 1));
93    if let Some(title) = &options.title {
94        panel = panel.title(title.clone());
95    }
96    panel
97}