Avoid cloning hyperlink text during TUI rendering (#34366)

## What changed

Add a borrowed `Line` conversion and use it when rendering and measuring
hyperlink-aware history and pager content. This preserves line styling and
alignment while avoiding clones of the underlying span strings.

GitOrigin-RevId: c9cd6ff5ec63cd29719ea2ae51acede7364f2bd8
This commit is contained in:
Charlie Marsh
2026-07-20 15:39:31 +00:00
committed by copyberry
parent f944456d81
commit 28aacbb9d9
4 changed files with 32 additions and 6 deletions
+2 -1
View File
@@ -37,6 +37,7 @@ use crate::terminal_hyperlinks::mark_buffer_hyperlinks;
use crate::terminal_hyperlinks::plain_hyperlink_lines;
use crate::terminal_hyperlinks::prefix_hyperlink_lines;
use crate::terminal_hyperlinks::visible_lines;
use crate::terminal_hyperlinks::visible_lines_ref;
#[cfg(test)]
use crate::test_support::PathBufExt;
#[cfg(test)]
@@ -309,7 +310,7 @@ pub(crate) trait HistoryCell: std::fmt::Debug + Send + Sync + Any {
impl Renderable for Box<dyn HistoryCell> {
fn render(&self, area: Rect, buf: &mut Buffer) {
let hyperlink_lines = self.display_hyperlink_lines(area.width);
let lines = visible_lines(hyperlink_lines.clone());
let lines = visible_lines_ref(&hyperlink_lines);
let paragraph = Paragraph::new(Text::from(lines)).wrap(Wrap { trim: false });
let y = if area.height == 0 {
0
+4 -4
View File
@@ -31,7 +31,7 @@ use crate::render::renderable::Renderable;
use crate::style::user_message_style;
use crate::terminal_hyperlinks::HyperlinkLine;
use crate::terminal_hyperlinks::mark_buffer_hyperlinks;
use crate::terminal_hyperlinks::visible_lines;
use crate::terminal_hyperlinks::visible_lines_ref;
use crate::tui;
use crate::tui::TuiEvent;
use crossterm::event::KeyCode;
@@ -408,7 +408,7 @@ impl Renderable for CellRenderable {
} else {
Style::default()
};
let p = Paragraph::new(Text::from(visible_lines(hyperlink_lines.clone())))
let p = Paragraph::new(Text::from(visible_lines_ref(&hyperlink_lines)))
.style(style)
.wrap(Wrap { trim: false });
p.render(area, buf);
@@ -426,14 +426,14 @@ struct HyperlinkLinesRenderable {
impl Renderable for HyperlinkLinesRenderable {
fn render(&self, area: Rect, buf: &mut Buffer) {
Paragraph::new(Text::from(visible_lines(self.lines.clone())))
Paragraph::new(Text::from(visible_lines_ref(&self.lines)))
.wrap(Wrap { trim: false })
.render(area, buf);
mark_buffer_hyperlinks(buf, area, &self.lines, /*scroll_rows*/ 0);
}
fn desired_height(&self, width: u16) -> u16 {
Paragraph::new(Text::from(visible_lines(self.lines.clone())))
Paragraph::new(Text::from(visible_lines_ref(&self.lines)))
.wrap(Wrap { trim: false })
.line_count(width)
.try_into()
+16
View File
@@ -1,6 +1,22 @@
use ratatui::text::Line;
use ratatui::text::Span;
/// Create a ratatui `Line` that borrows the contents of another line.
pub fn line_to_borrowed<'a>(line: &'a Line<'_>) -> Line<'a> {
Line {
style: line.style,
alignment: line.alignment,
spans: line
.spans
.iter()
.map(|span| Span {
style: span.style,
content: std::borrow::Cow::Borrowed(span.content.as_ref()),
})
.collect(),
}
}
/// Clone a borrowed ratatui `Line` into an owned `'static` line.
pub fn line_to_static(line: &Line<'_>) -> Line<'static> {
Line {
+10 -1
View File
@@ -19,6 +19,7 @@ use unicode_width::UnicodeWidthChar;
use unicode_width::UnicodeWidthStr;
use url::Url;
use crate::render::line_utils::line_to_borrowed;
use crate::render::line_utils::line_to_static;
use crate::wrapping::RtOptions;
use crate::wrapping::adaptive_wrap_line;
@@ -127,6 +128,13 @@ pub(crate) fn visible_lines(lines: Vec<HyperlinkLine>) -> Vec<Line<'static>> {
lines.into_iter().map(|line| line.line).collect()
}
pub(crate) fn visible_lines_ref(lines: &[HyperlinkLine]) -> Vec<Line<'_>> {
lines
.iter()
.map(|line| line_to_borrowed(&line.line))
.collect()
}
pub(crate) fn plain_hyperlink_lines(lines: Vec<Line<'static>>) -> Vec<HyperlinkLine> {
lines.into_iter().map(HyperlinkLine::new).collect()
}
@@ -483,7 +491,8 @@ pub(crate) fn mark_buffer_hyperlinks(
}
let mut logical_row = 0usize;
for line in lines {
let paragraph = Paragraph::new(Text::from(line.line.clone())).wrap(Wrap { trim: false });
let paragraph =
Paragraph::new(Text::from(line_to_borrowed(&line.line))).wrap(Wrap { trim: false });
let rendered_height = paragraph.line_count(area.width).max(/*other*/ 1);
if line.hyperlinks.is_empty() {
logical_row += rendered_height;