node_launchpad/widgets/
hyperlink.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use itertools::Itertools;
10use ratatui::{prelude::*, widgets::WidgetRef};
11
12/// A hyperlink widget that renders a hyperlink in the terminal using [OSC 8].
13///
14/// [OSC 8]: https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
15pub struct Hyperlink<'content> {
16    text: Text<'content>,
17    url: String,
18}
19
20impl<'content> Hyperlink<'content> {
21    pub fn new(text: impl Into<Text<'content>>, url: impl Into<String>) -> Self {
22        Self {
23            text: text.into(),
24            url: url.into(),
25        }
26    }
27}
28
29impl WidgetRef for Hyperlink<'_> {
30    fn render_ref(&self, area: Rect, buffer: &mut Buffer) {
31        self.text.render_ref(area, buffer);
32
33        // this is a hacky workaround for https://github.com/ratatui-org/ratatui/issues/902, a bug
34        // in the terminal code that incorrectly calculates the width of ANSI escape sequences. It
35        // works by rendering the hyperlink as a series of 2-character chunks, which is the
36        // calculated width of the hyperlink text.
37        for (i, two_chars) in self
38            .text
39            .to_string()
40            .chars()
41            .chunks(2)
42            .into_iter()
43            .enumerate()
44        {
45            let text = two_chars.collect::<String>();
46            let hyperlink = format!("\x1B]8;;{}\x07{}\x1B]8;;\x07", self.url, text);
47            buffer
48                .cell_mut(Position::new(area.x + i as u16 * 2, area.y))
49                .map(|cell| cell.set_symbol(hyperlink.as_str()));
50        }
51    }
52}