//! # [Ratatui] Hyperlink examplew //! //! Shows how to use [OSC 8] to create hyperlinks in the terminal. //! //! The latest version of this example is available in the [examples] folder in the repository. //! //! Please note that the examples are designed to be run against the `main` branch of the Github //! repository. This means that you may not be able to compile with the latest release version on //! crates.io, or the one that you have installed locally. //! //! See the [examples readme] for more information on finding examples that match the version of the //! library you are using. //! //! [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda //! [Ratatui]: https://github.com/ratatui/ratatui //! [examples]: https://github.com/ratatui/ratatui/blob/main/examples //! [examples readme]: https://github.com/ratatui/ratatui/blob/main/examples/README.md use color_eyre::Result; use itertools::Itertools; use ratatui::{ buffer::Buffer, crossterm::event::{self, Event, KeyCode}, layout::Rect, style::Stylize, text::{Line, Text}, widgets::Widget, DefaultTerminal, }; fn main() -> Result<()> { color_eyre::install()?; let terminal = ratatui::init(); let app_result = App::new().run(terminal); ratatui::restore(); app_result } struct App { hyperlink: Hyperlink<'static>, } impl App { fn new() -> Self { let text = Line::from(vec!["Example ".into(), "hyperlink".blue()]); let hyperlink = Hyperlink::new(text, "https://example.com"); Self { hyperlink } } fn run(self, mut terminal: DefaultTerminal) -> Result<()> { loop { terminal.draw(|frame| frame.render_widget(&self.hyperlink, frame.area()))?; if let Event::Key(key) = event::read()? { if matches!(key.code, KeyCode::Char('q') | KeyCode::Esc) { break; } } } Ok(()) } } /// A hyperlink widget that renders a hyperlink in the terminal using [OSC 8]. /// /// [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda struct Hyperlink<'content> { text: Text<'content>, url: String, } impl<'content> Hyperlink<'content> { fn new(text: impl Into>, url: impl Into) -> Self { Self { text: text.into(), url: url.into(), } } } impl Widget for &Hyperlink<'_> { fn render(self, area: Rect, buffer: &mut Buffer) { (&self.text).render(area, buffer); // this is a hacky workaround for https://github.com/ratatui/ratatui/issues/902, a bug // in the terminal code that incorrectly calculates the width of ANSI escape sequences. It // works by rendering the hyperlink as a series of 2-character chunks, which is the // calculated width of the hyperlink text. for (i, two_chars) in self .text .to_string() .chars() .chunks(2) .into_iter() .enumerate() { let text = two_chars.collect::(); let hyperlink = format!("\x1B]8;;{}\x07{}\x1B]8;;\x07", self.url, text); buffer[(area.x + i as u16 * 2, area.y)].set_symbol(hyperlink.as_str()); } } }