Skip to content

Conversation

@jetpham
Copy link
Contributor

@jetpham jetpham commented Apr 7, 2025

Add the ability to add aliases to hyperlinks like markdown. Changes the Hyperlink::new() function to accept a content field and a url field. This ability comes from an ability I added to ratatui 0.29.0 to add a hyperlink field to Cell. This is accessed through the style, where you can get, and set the hyperlink.

A Cell's hyperlink is an Option<'static str>. I tried making it not static but ran into endless lifetime issues (could possibly be fixed in the future).

Also prerender function in the dom is made simpler because now, We can just move the adding of a nested into each span if cell.hyperlink.is_some()

/// Pre-render the content to the screen.
    ///
    /// This function is called from [`flush`] once to render the initial
    /// content to the screen.
    fn prerender(&mut self) -> Result<(), Error> {
        for line in self.buffer.iter() {
            let mut line_cells: Vec<Element> = Vec::new();
            for cell in line.iter() {
                let span = create_span(&self.document, cell)?;
                self.cells.push(span.clone());
                line_cells.push(span);
            }

            // Create a <pre> element for the line
            let pre = self.document.create_element("pre")?;

            // Append all elements (spans and anchors) to the <pre>
            for elem in line_cells {
                pre.append_child(&elem)?;
            }

            // Append the <pre> to the grid
            self.grid.append_child(&pre)?;
        }
        Ok(())
    }
    ```
    
    The create_span function juts adds a nested <a> tag with the hyperlink if there is one:
    ```rust
    /// Creates a new `<span>` element with the given cell.
pub(crate) fn create_span(document: &Document, cell: &Cell) -> Result<Element, Error> {
    let span = document.create_element("span")?;

    let style = get_cell_style_as_css(cell);
    span.set_attribute("style", &style)?;

    if let Some(url) = cell.hyperlink() {
        let anchor = document.create_element("a")?;
        anchor.set_attribute("href", url)?;
        anchor.set_inner_html(cell.symbol());
        span.append_child(&anchor)?;
    } else {
        span.set_inner_html(cell.symbol());
    }
    Ok(span)
}

This is still a draft because I have yet to get this all to work. Actively debugging this has led me to find that actually setting the hyperlink field is not working, but reading it and rendering the nested tags is totally fine. example:
image

@jetpham
Copy link
Contributor Author

jetpham commented Apr 7, 2025

This should probably not be added to the main branch. This completely removes the serde feature from ratatui which is probably not supported. I would like to know the best way to go about this. This is my first larger open source contribution.

@jetpham jetpham force-pushed the aliasable-hyperlinks branch 3 times, most recently from d3d1f03 to 9f740e6 Compare April 7, 2025 23:59
@jetpham
Copy link
Contributor Author

jetpham commented Apr 8, 2025

This currently works now, I have an example on my site as of Apr 7 2025

Some pros and cons of my implementation:

Pros:

  • Adds aliasable link support.
  • Allows hyperlinks to be in a tag, so operations like buffer_mut() don't have strange behavior when their style is being changed: Screencast (It should be changing colors as the cells around it change color)
    it's commit.

Cons:

  • Required me to delete the Serde feature from ratatui due to the lifetime requirements of the Deserialize trait.
  • The dom has to render more elements and the are seperated out per span rather than covering all the spans they apply to, but personally I see this as more consistant with how the styles are rendered out:
    Original:
<span style="a"></span>
<a href="...">
    <span style="b"></span>
    <span style="b"></span>
</a>
<span style="c"></span>

Mine:

<span style="a"></span>
<span style="b">
    <a href="..."></a>
</span>
<span style="b">
    <a href="..."></a>
</span>
<span style="c"></span>
  • Probably some other things because I'm not very experienced in open source contributing to rust projects

@jetpham jetpham force-pushed the aliasable-hyperlinks branch from 9f740e6 to e7f36ec Compare April 8, 2025 02:03
@jetpham jetpham force-pushed the aliasable-hyperlinks branch from 9b6b378 to 52a13ba Compare April 8, 2025 02:14
ratatui = { git = "https://github.com/jetpham/ratatui.git", branch = "hyperlink", version = "0.29", default-features = false, features = ["all-widgets"] }
console_error_panic_hook = "0.1.7"
thiserror = "2.0.12"
log = "0.4.27"
Copy link
Owner

Choose a reason for hiding this comment

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

You can use console::log_1 for logging in the browser btw :) no need for log

@orhun
Copy link
Owner

orhun commented Apr 8, 2025

Thanks for looking into this! I'm guessing this is similar to what has described in #6, right?

The problem with the current approach is also described there... it works in a very hacky way. In a nutshell, we don't have any communication between the rendered widget and the render function. I guess that's why you went with adding a hyperlink to the Cell. Correct me if I'm wrong here :)

I was thinking of storing a static global state for the things that need to be passed to the render function. So whenever you construct a Hyperlink, we'll store the area (coordinates) and other data (content, url) and just access them in render for proper rendering.

But I'm still thinking about this... your approach definitely works but I'd rather we touch ratatui as less as possible here :)

WDYT?

@jetpham
Copy link
Contributor Author

jetpham commented Apr 8, 2025

I definitely agree with touching ratatui less. I did the hyperlink approach because of the difficulty of embedding the link data as a style without it being the content, so that's why I went with changing ratatui. But I like the idea of storing a "mask" of which coordinates are hyperlinks, and in the render pass we add a tags to spans that match coords with that hyperlink mask. This would mean the Hyperlink::new() would store the span of text, and then the hyperlink in the Hyperlink struct, then in rendering, it somehow passes that information through while still retaining the rect for that hyperlink. I just don't understand the way that ratatui deals with rendering widgets quite enough to implement that yet, but I might give it a shot, because I'd rather be using a stable version of ratatui.

@orhun
Copy link
Owner

orhun commented Apr 9, 2025

I just don't understand the way that ratatui deals with rendering widgets quite enough to implement that yet,

I suggest taking a look at rendering under the hood for the details. But in a nutshell, Ratatui does not store any data about widgets, it simply passes their final visual representation to the backend as "cells" and let the backend does its magic. Whereas in our case we need to store some additional data.

but I might give it a shot, because I'd rather be using a stable version of ratatui.

That would be great! Let me know if you find a better way etc. - feedback is always appreciated!

@orhun
Copy link
Owner

orhun commented Apr 20, 2025

Hey, any updates on this? @jetpham

Maybe want to take a look at this together sometime?

@jetpham
Copy link
Contributor Author

jetpham commented Apr 20, 2025

Hey! I will be back on this. But no updates on progress. But I do really like the idea of having a second mask layer that contains hyperlink information. I think there's a way to also make it not break getting the raw buffer. I'll have time to play with this soon, but I'm in midterm season. I'll let you know if you have any updates, please do at me if you have any ideas.

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.

2 participants