Skip to content

Commit d3d1f03

Browse files
committed
feat: add aliasable hyperlinks
1 parent 3b76214 commit d3d1f03

7 files changed

Lines changed: 62 additions & 60 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ web-sys = { version = "0.3.77", features = [
2727
'HtmlCanvasElement',
2828
'Location',
2929
] }
30-
ratatui = { version = "0.29", default-features = false, features = ["all-widgets"] }
30+
ratatui = { git = "git@github.com:jetpham/ratatui.git", branch = "hyperlink", version = "0.29", default-features = false, features = ["all-widgets"] }
3131
console_error_panic_hook = "0.1.7"
3232
thiserror = "2.0.12"
33+
log = "0.4.27"

examples/website/Cargo.lock

Lines changed: 21 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/website/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ fn render_intro(f: &mut Frame<'_>, state: &mut State) {
9090
Line::from("Stomping through the web").italic(),
9191
]);
9292
f.render_widget(main_text.light_green().centered(), area);
93-
let link = Hyperlink::new("https://github.com/orhun/ratzilla".red());
93+
let link = Hyperlink::new("ratzilla".red(), "https://github.com/orhun/ratzilla");
9494
f.render_widget(link, area.offset(Offset { x: 0, y: 4 }));
9595
f.render_effect(&mut state.intro_effect, area, Duration::from_millis(40));
9696
}

src/backend/dom.rs

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use web_sys::{
1111
window, Document, Element, Window,
1212
};
1313

14-
use crate::{backend::utils::*, error::Error, widgets::hyperlink::HYPERLINK_MODIFIER};
14+
use crate::{backend::utils::*, error::Error};
1515

1616
/// Options for the [`DomBackend`].
1717
#[derive(Debug, Default)]
@@ -30,7 +30,7 @@ impl DomBackendOptions {
3030
///
3131
/// - If the grid ID is not set, it returns `"grid"`.
3232
/// - If the grid ID is set, it returns the grid ID suffixed with
33-
/// `"_ratzilla_grid"`.
33+
/// `"_ratzilla_grid"`.
3434
pub fn grid_id(&self) -> String {
3535
match &self.grid_id {
3636
Some(id) => format!("{id}_ratzilla_grid"),
@@ -131,30 +131,10 @@ impl DomBackend {
131131
fn prerender(&mut self) -> Result<(), Error> {
132132
for line in self.buffer.iter() {
133133
let mut line_cells: Vec<Element> = Vec::new();
134-
let mut hyperlink: Vec<Cell> = Vec::new();
135-
for (i, cell) in line.iter().enumerate() {
136-
if cell.modifier.contains(HYPERLINK_MODIFIER) {
137-
hyperlink.push(cell.clone());
138-
// If the next cell is not part of the hyperlink, close it
139-
if !line
140-
.get(i + 1)
141-
.map(|c| c.modifier.contains(HYPERLINK_MODIFIER))
142-
.unwrap_or(false)
143-
{
144-
let anchor = create_anchor(&self.document, &hyperlink)?;
145-
for link_cell in &hyperlink {
146-
let span = create_span(&self.document, link_cell)?;
147-
self.cells.push(span.clone());
148-
anchor.append_child(&span)?;
149-
}
150-
line_cells.push(anchor);
151-
hyperlink.clear();
152-
}
153-
} else {
154-
let span = create_span(&self.document, cell)?;
155-
self.cells.push(span.clone());
156-
line_cells.push(span);
157-
}
134+
for cell in line.iter() {
135+
let span = create_span(&self.document, cell)?;
136+
self.cells.push(span.clone());
137+
line_cells.push(span);
158138
}
159139

160140
// Create a <pre> element for the line
@@ -176,13 +156,20 @@ impl DomBackend {
176156
fn update_grid(&mut self) -> Result<(), Error> {
177157
for (y, line) in self.buffer.iter().enumerate() {
178158
for (x, cell) in line.iter().enumerate() {
179-
if cell.modifier.contains(HYPERLINK_MODIFIER) {
180-
continue;
181-
}
182159
if cell != &self.prev_buffer[y][x] {
183160
let elem = self.cells[y * self.buffer[0].len() + x].clone();
184-
elem.set_inner_html(cell.symbol());
185161
elem.set_attribute("style", &get_cell_style_as_css(cell))?;
162+
if let Some(anchor) = elem.first_element_child() {
163+
if let Some(url) = cell.hyperlink() {
164+
anchor.set_attribute("href", url)?;
165+
anchor.set_inner_html(cell.symbol());
166+
} else {
167+
anchor.remove();
168+
elem.set_inner_html(cell.symbol());
169+
}
170+
} else {
171+
elem.set_inner_html(cell.symbol());
172+
}
186173
}
187174
}
188175
}

src/backend/utils.rs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,19 @@ use crate::{
1212
/// Creates a new `<span>` element with the given cell.
1313
pub(crate) fn create_span(document: &Document, cell: &Cell) -> Result<Element, Error> {
1414
let span = document.create_element("span")?;
15-
span.set_inner_html(cell.symbol());
1615

1716
let style = get_cell_style_as_css(cell);
1817
span.set_attribute("style", &style)?;
19-
Ok(span)
20-
}
2118

22-
/// Creates a new `<a>` element with the given cells.
23-
pub(crate) fn create_anchor(document: &Document, cells: &[Cell]) -> Result<Element, Error> {
24-
let anchor = document.create_element("a")?;
25-
anchor.set_attribute(
26-
"href",
27-
&cells.iter().map(|c| c.symbol()).collect::<String>(),
28-
)?;
29-
anchor.set_attribute("style", &get_cell_style_as_css(&cells[0]))?;
30-
Ok(anchor)
19+
if let Some(url) = cell.hyperlink() {
20+
let anchor = document.create_element("a")?;
21+
anchor.set_attribute("href", url)?;
22+
anchor.set_inner_html(cell.symbol());
23+
span.append_child(&anchor)?;
24+
} else {
25+
span.set_inner_html(cell.symbol());
26+
}
27+
Ok(span)
3128
}
3229

3330
/// Converts a cell to a CSS style.

src/widgets/hyperlink.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
use ratatui::{buffer::Buffer, layout::Rect, style::Modifier, text::Span, widgets::Widget};
2-
3-
/// Hyperlink modifier.
4-
///
5-
/// When added as a modifier to a style, the styled element is marked as
6-
/// hyperlink.
7-
pub(crate) const HYPERLINK_MODIFIER: Modifier = Modifier::SLOW_BLINK;
1+
use log::info;
2+
use ratatui::{buffer::Buffer, layout::Rect, style::Style, text::Span, widgets::Widget};
83

94
/// A widget that can be used to render hyperlinks.
105
///
@@ -16,20 +11,24 @@ pub(crate) const HYPERLINK_MODIFIER: Modifier = Modifier::SLOW_BLINK;
1611
/// // Then you can render it as usual:
1712
/// // frame.render_widget(link, frame.area());
1813
/// ```
14+
#[derive(Debug)]
1915
pub struct Hyperlink<'a> {
2016
/// Line.
2117
line: Span<'a>,
2218
}
2319

2420
impl<'a> Hyperlink<'a> {
2521
/// Constructs a new [`Hyperlink`] widget.
26-
pub fn new<T>(url: T) -> Self
22+
pub fn new<T, U>(content: T, url: U) -> Self
2723
where
2824
T: Into<Span<'a>>,
25+
U: Into<&'static str>,
2926
{
30-
Self {
31-
line: url.into().style(HYPERLINK_MODIFIER),
32-
}
27+
let line = content
28+
.into()
29+
.patch_style(Style::new().hyperlink(url.into()));
30+
// info!("span: {:#?}", line);
31+
Self { line }
3332
}
3433
}
3534

0 commit comments

Comments
 (0)