Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(text): add push methods for text and line #998

Merged
merged 2 commits into from
Mar 28, 2024
Merged

feat(text): add push methods for text and line #998

merged 2 commits into from
Mar 28, 2024

Conversation

joshka
Copy link
Member

@joshka joshka commented Mar 25, 2024

Adds the following methods to the Text and Line structs:

  • Text::push_line
  • Text::push_span
  • Line::push_span

This allows for adding lines and spans to a text object without having
to call methods on the fields directly, which is usefult for incremental
construction of text objects.

Adds the following methods to the `Text` and `Line` structs:
- Text::push_line
- Text::push_span
- Line::push_span

This allows for adding lines and spans to a text object without having
to call methods on the fields directly, which is usefult for incremental
construction of text objects.
Copy link

codecov bot commented Mar 25, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 89.4%. Comparing base (742a5ea) to head (65446f0).
Report is 2 commits behind head on main.

Additional details and impacted files
@@          Coverage Diff          @@
##            main    #998   +/-   ##
=====================================
  Coverage   89.3%   89.4%           
=====================================
  Files         61      61           
  Lines      15298   15359   +61     
=====================================
+ Hits       13670   13731   +61     
  Misses      1628    1628           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@joshka
Copy link
Member Author

joshka commented Mar 25, 2024

The underlying use case that prompted this change is the following code that defines a JsonWidget. This code is significantly simplified from the equivalent that calls into the fields, as there are problems borrowing the fields mutably multiple times.

pub struct JsonWidget {
    json: Value,
}

const PUNCTUATION_STYLE: Style = Style::new().add_modifier(Modifier::BOLD).fg(Color::White);
const KEY_STYLE: Style = Style::new().add_modifier(Modifier::BOLD).fg(Color::Blue);
const STRING_STYLE: Style = Style::new().fg(Color::Green);
const NUMBER_STYLE: Style = Style::new().fg(Color::Yellow);
const BOOLEAN_STYLE: Style = Style::new().fg(Color::Cyan);
const NULL_STYLE: Style = Style::new().add_modifier(Modifier::DIM);

impl Widget for &JsonWidget {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let mut text = Text::raw("");
        push_value(&self.json, &mut text, 0);
        text.render(area, buf);
    }
}

fn push_value(value: &Value, text: &mut Text, indent: usize) {
    match value {
        Value::Null => push_null(text),
        Value::Bool(b) => push_bool(text, *b),
        Value::Number(num) => push_number(text, num),
        Value::String(s) => push_string(text, s),
        Value::Array(arr) => push_array(text, arr, indent),
        Value::Object(map) => push_object(text, map, indent),
    };
}

fn push_null(text: &mut Text) {
    let null = Span::styled("null", NULL_STYLE);
    text.push_span(null);
}

fn push_bool(text: &mut Text, b: bool) {
    let bool = Span::styled(b.to_string(), BOOLEAN_STYLE);
    text.push_span(bool);
}

fn push_number(text: &mut Text, num: &Number) {
    let num = Span::styled(format!("{}", num), NUMBER_STYLE);
    text.push_span(num);
}

fn push_string(text: &mut Text, s: &str) {
    let string = Span::styled(format!("\"{}\"", s), STRING_STYLE);
    text.push_span(string);
}

fn push_array(text: &mut Text, arr: &[Value], indent: usize) {
    let open_bracket = Span::styled("[", PUNCTUATION_STYLE);
    let close_bracket = Span::styled("]", PUNCTUATION_STYLE);
    let comma = Span::styled(", ", PUNCTUATION_STYLE);

    text.push_span(open_bracket);
    for (i, value) in arr.iter().enumerate() {
        if i > 0 {
            text.push_span(comma.clone());
        }
        text.push_line(" ".repeat(indent + 2).into());
        push_value(value, text, indent + 2);
    }
    if !arr.is_empty() {
        text.push_line(" ".repeat(indent).into());
    }
    text.push_span(close_bracket)
}

fn push_object(text: &mut Text, map: &Map<String, Value>, indent: usize) {
    let open_brace = Span::styled("{", PUNCTUATION_STYLE);
    let close_brace = Span::styled("}", PUNCTUATION_STYLE);
    let comma = Span::styled(", ", PUNCTUATION_STYLE);
    let colon = Span::styled(": ", PUNCTUATION_STYLE);

    text.push_span(open_brace);
    for (i, (key, value)) in map.iter().enumerate() {
        let key = Span::styled(format!("\"{}\"", key), KEY_STYLE);
        if i > 0 {
            text.push_span(comma.clone());
        }
        text.push_line(" ".repeat(indent + 2).into());
        text.push_span(key);
        text.push_span(colon.clone());
        push_value(value, text, indent + 2);
    }
    if !map.is_empty() {
        text.push_line(" ".repeat(indent).into());
    }
    text.push_span(close_brace)
}
image

Copy link
Sponsor Member

@orhun orhun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks convenient to me

@joshka
Copy link
Member Author

joshka commented Mar 25, 2024

I'll wait on a second review for some feedback on this one as it's a change to the APIs which could do with some input. I think these are the most likely the right names for this, but I was wrong about the to_xxx_aligned() method names recently.

Copy link
Member

@EdJoPaTo EdJoPaTo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I try to create something only once I have all the parts for it together. So in this case I would have started with a Vec<Line> which I only put into the Text as a last step. But especially with the push_span these are more helpful in this case.


My mqttui displays JSON within my tree widget and a combination of both might be neat to have. (See payload_view.rs, json.rs and json_selector.rs). Now I'm curious about improving the output of that to look more like yours (while maintaining the tree like navigation) 🙃

src/text/text.rs Outdated Show resolved Hide resolved
@joshka
Copy link
Member Author

joshka commented Mar 26, 2024

Personally I try to create something only once I have all the parts for it together. So in this case I would have started with a Vec which I only put into the Text as a last step. But especially with the push_span these are more helpful in this case.

Yeah, that's reasonable I guess and for making things editable it might be the better approach for the JSON editor tutorial anyway especially because the selection needs to track which spans are editable and which are not. For that I'm trying to simplify the code needed to create an editor as much as possible. The idea there being more about showing how to make an app interactive than about making it pretty (but making it pretty is an important secondary goal).

I would probably use a tree widget in a real app for this sort of thing, but generally try to avoid third party things for tutorial code.

@joshka joshka merged commit 26af650 into main Mar 28, 2024
33 checks passed
@joshka joshka deleted the push-text branch March 28, 2024 22:30
joshka added a commit to nowNick/ratatui that referenced this pull request May 24, 2024
Adds the following methods to the `Text` and `Line` structs:
- Text::push_line
- Text::push_span
- Line::push_span

This allows for adding lines and spans to a text object without having
to call methods on the fields directly, which is usefult for incremental
construction of text objects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants